dream_test/unit

Unit test DSL (no explicit context).

This is the default DSL for most users: describe + it with 0‑argument test bodies.

This module builds a dream_test/types.TestSuite(Nil) under the hood.

When should I use this module?

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 returns Ok(Nil) or Error(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 returns Ok(Nil) or Error(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 become SetupFailed.

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 returns Ok(Nil) on success or Error(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 becomes SetupFailed and 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 returns Ok(Nil) on success or Error(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 in runner.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 returns Result(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 name
  • run: 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.

Search Document