dream_test/unit
Unit test DSL (no explicit context).
This is the default DSL for most users: describe + it with 0‑argument
test bodies.
- Tests are written as
it("does something", fn() { ... }) - Hooks are also 0‑argument functions (
before_each(fn() { ... })) - All hooks/tests return
Result(AssertionResult, String)so you can abort early withError("message")when prerequisites fail.
This module builds a dream_test/types.TestSuite(Nil) under the hood.
When should I use this module?
- Use
dream_test/unitfor most unit tests. - Use
dream_test/unit_contextonly when you want a real context value threaded through hooks and test bodies.
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, it}
import gleam/string
pub fn tests() {
describe("String utilities", [
it("trims whitespace", fn() {
" hello "
|> string.trim()
|> should
|> be_equal("hello")
|> or_fail_with("Should remove surrounding whitespace")
}),
it("finds substrings", fn() {
"hello world"
|> string.contains("world")
|> should
|> be_equal(True)
|> or_fail_with("Should find 'world' in string")
}),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Types
A Node(Nil) built using the dream_test/unit DSL.
You generally don’t need to construct nodes directly; use group, it,
and the hook helpers in this module.
Parameters
UnitNode is an alias of types.Node(Nil), where the context type is Nil.
This module creates these nodes for you.
pub type UnitNode =
types.Node(Nil)
Values
pub fn after_all(
teardown teardown: fn() -> Result(Nil, String),
) -> types.Node(Nil)
Run once after all tests in the current scope.
Example
import dream_test/matchers.{be_empty, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{
after_all, after_each, before_all, before_each, describe, it,
}
import gleam/io
pub fn tests() {
describe("Database tests", [
before_all(fn() {
// Start database once for all tests
start_database()
}),
before_each(fn() {
// Begin transaction before each test
begin_transaction()
}),
it("creates a record", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
it("queries records", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
after_each(fn() {
// Rollback transaction after each test
rollback_transaction()
}),
after_all(fn() {
// Stop database after all tests
stop_database()
}),
])
}
fn start_database() {
Ok(Nil)
}
fn stop_database() {
Ok(Nil)
}
fn begin_transaction() {
Ok(Nil)
}
fn rollback_transaction() {
Ok(Nil)
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
teardown: a 0-argument function that returnsOk(Nil)orError(message)
Returns
A UnitNode representing an after_all hook.
pub fn after_each(
teardown teardown: fn() -> Result(Nil, String),
) -> types.Node(Nil)
Run after each test in the current scope.
This is useful for cleanup that must always run (even after assertion failures).
Example
import dream_test/matchers.{be_empty, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{
after_all, after_each, before_all, before_each, describe, it,
}
import gleam/io
pub fn tests() {
describe("Database tests", [
before_all(fn() {
// Start database once for all tests
start_database()
}),
before_each(fn() {
// Begin transaction before each test
begin_transaction()
}),
it("creates a record", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
it("queries records", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
after_each(fn() {
// Rollback transaction after each test
rollback_transaction()
}),
after_all(fn() {
// Stop database after all tests
stop_database()
}),
])
}
fn start_database() {
Ok(Nil)
}
fn stop_database() {
Ok(Nil)
}
fn begin_transaction() {
Ok(Nil)
}
fn rollback_transaction() {
Ok(Nil)
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
teardown: a 0-argument function that returnsOk(Nil)orError(message)
Returns
A UnitNode representing an after_each hook.
pub fn before_all(
setup setup: fn() -> Result(Nil, String),
) -> types.Node(Nil)
Run once before any tests in the current suite/group.
- Runs in a sandboxed process.
- If it returns
Error("message"), all tests under this scope becomeSetupFailed.
Example
import dream_test/matchers.{be_empty, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{
after_all, after_each, before_all, before_each, describe, it,
}
import gleam/io
pub fn tests() {
describe("Database tests", [
before_all(fn() {
// Start database once for all tests
start_database()
}),
before_each(fn() {
// Begin transaction before each test
begin_transaction()
}),
it("creates a record", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
it("queries records", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
after_each(fn() {
// Rollback transaction after each test
rollback_transaction()
}),
after_all(fn() {
// Stop database after all tests
stop_database()
}),
])
}
fn start_database() {
Ok(Nil)
}
fn stop_database() {
Ok(Nil)
}
fn begin_transaction() {
Ok(Nil)
}
fn rollback_transaction() {
Ok(Nil)
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
setup: a 0-argument function that returnsOk(Nil)on success orError(message)on failure
Returns
A UnitNode representing a before_all hook.
pub fn before_each(
setup setup: fn() -> Result(Nil, String),
) -> types.Node(Nil)
Run before each test in the current scope.
- Runs in a sandboxed process.
- If it returns
Error("message"), that test becomesSetupFailedand the body does not run.
Example
import dream_test/matchers.{be_empty, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{
after_all, after_each, before_all, before_each, describe, it,
}
import gleam/io
pub fn tests() {
describe("Database tests", [
before_all(fn() {
// Start database once for all tests
start_database()
}),
before_each(fn() {
// Begin transaction before each test
begin_transaction()
}),
it("creates a record", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
it("queries records", fn() {
[]
|> should
|> be_empty()
|> or_fail_with("Placeholder test")
}),
after_each(fn() {
// Rollback transaction after each test
rollback_transaction()
}),
after_all(fn() {
// Stop database after all tests
stop_database()
}),
])
}
fn start_database() {
Ok(Nil)
}
fn stop_database() {
Ok(Nil)
}
fn begin_transaction() {
Ok(Nil)
}
fn rollback_transaction() {
Ok(Nil)
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
setup: a 0-argument function that returnsOk(Nil)on success orError(message)on failure
Returns
A UnitNode representing a before_each hook.
pub fn describe(
name name: String,
children children: List(types.Node(Nil)),
) -> types.Root(Nil)
Create a top-level test suite.
The returned value is what you pass to runner.new([ ... ]).
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, it}
import gleam/string
pub fn tests() {
describe("String utilities", [
it("trims whitespace", fn() {
" hello "
|> string.trim()
|> should
|> be_equal("hello")
|> or_fail_with("Should remove surrounding whitespace")
}),
it("finds substrings", fn() {
"hello world"
|> string.contains("world")
|> should
|> be_equal(True)
|> or_fail_with("Should find 'world' in string")
}),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
name: the suite name (shown in reports)children: the suite contents (tests, groups, and hooks)
Returns
A TestSuite(Nil) you can pass to runner.new([ ... ]).
pub fn group(
name name: String,
children children: List(types.Node(Nil)),
) -> types.Node(Nil)
Create a nested group inside a suite.
Groups provide structure (and hook scoping). Hooks declared in an outer group apply to tests in inner groups.
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, group, it}
import gleam/io
pub fn tests() {
describe("Calculator", [
group("addition", [
it("adds small numbers", fn() {
2 + 3
|> should
|> be_equal(5)
|> or_fail_with("2 + 3 should equal 5")
}),
it("adds negative numbers", fn() {
-2 + -3
|> should
|> be_equal(-5)
|> or_fail_with("-2 + -3 should equal -5")
}),
]),
group("division", [
it("integer division rounds toward zero", fn() {
7 / 2
|> should
|> be_equal(3)
|> or_fail_with("7 / 2 should equal 3")
}),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
name: the group name (shown in reports and inrunner.TestInfo.full_name)children: nested tests/groups/hooks under this group
Returns
A UnitNode you include in a parent describe/group children list.
pub fn it(
name name: String,
run run: fn() -> Result(types.AssertionResult, String),
) -> types.Node(Nil)
Define a single test case.
- The body is 0-arg (
fn() { ... }) - Return
Ok(...)to indicate an assertion result - Return
Error("message")to abort the test with a message
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, it}
import gleam/io
import gleam/string
pub fn tests() {
describe("String utilities", [
it("trims whitespace", fn() {
" hello "
|> string.trim()
|> should
|> be_equal("hello")
|> or_fail_with("Should remove surrounding whitespace")
}),
it("finds substrings", fn() {
"hello world"
|> string.contains("world")
|> should
|> be_equal(True)
|> or_fail_with("Should find 'world' in string")
}),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
name: the test name (shown in reports)run: a 0-argument test body that returnsResult(AssertionResult, String)
Returns
A UnitNode representing the test.
pub fn skip(
name name: String,
run run: fn() -> Result(types.AssertionResult, String),
) -> types.Node(Nil)
Define a skipped test.
skip has the same shape as it so you can easily switch a test between
running and skipped without rewriting the test body.
The provided test body is preserved for that purpose, but it is not executed while the test is skipped.
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, it, skip}
import gleam/io
pub fn tests() {
describe("Skipping tests", [
it("runs normally", fn() {
2 + 3
|> should
|> be_equal(5)
|> or_fail_with("2 + 3 should equal 5")
}),
skip("not implemented yet", fn() {
// This test is skipped - the body is preserved but not executed
100 + 200
|> should
|> be_equal(300)
|> or_fail_with("Should add large numbers")
}),
it("also runs normally", fn() {
0 + 0
|> should
|> be_equal(0)
|> or_fail_with("0 + 0 should equal 0")
}),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
name: the test namerun: a 0-argument function (accepted but never executed)
Returns
A UnitNode representing a skipped test (AssertionSkipped).
pub fn with_tags(
node node: types.Node(Nil),
tags tags: List(String),
) -> types.Node(Nil)
Attach tags to a node.
Tags propagate to descendant tests and are included in TestResult.tags.
Use tags to filter executed tests (e.g. in CI) with runner.filter_tests.
Example
import dream_test/matchers.{succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import dream_test/unit.{describe, it, with_tags}
import gleam/io
pub fn tests() {
describe("Tagged tests", [
it("fast", fn() { Ok(succeed()) })
|> with_tags(["unit", "fast"]),
it("slow", fn() { Ok(succeed()) })
|> with_tags(["integration", "slow"]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Parameters
node: a test or group node to tag (tags do not apply to hooks)tags: tags to attach; group tags are inherited by descendant tests
Returns
The updated UnitNode with tags set.