dream_test/process

Process helpers for tests that need actors or async operations.

When Dream Test runs a test, it runs in an isolated BEAM process. Any processes you spawn inside a test can be linked to that test process, so they automatically die when the test ends (pass, fail, timeout, crash).

This module gives you a few “batteries included” patterns:

Example

Use this inside an it block.

let counter = process.start_counter()
process.increment(counter)
process.increment(counter)

process.get_count(counter)
|> should
|> be_equal(2)
|> or_fail_with("expected counter to be 2")

Types

Messages for the built-in counter actor.

Most of the time you’ll use the helpers (increment, set_count, etc), but you can also send the messages directly when it’s convenient.

Example

Use this inside an it block.

let counter = process.start_counter()

erlang_process.send(counter, process.Increment)
erlang_process.send(counter, process.SetCount(10))

process.get_count(counter)
|> should
|> be_equal(10)
|> or_fail_with("expected counter to be 10 after SetCount")
pub type CounterMessage {
  Increment
  Decrement
  SetCount(Int)
  GetCount(process.Subject(Int))
}

Constructors

Configuration for polling operations.

Controls how long to wait and how often to check.

Fields

  • timeout_ms - Maximum time to wait before giving up
  • interval_ms - How often to check the condition

Example

process.default_poll_config()
|> should
|> be_equal(process.PollConfig(timeout_ms: 5000, interval_ms: 50))
|> or_fail_with("expected default_poll_config to be 5000ms/50ms")
pub type PollConfig {
  PollConfig(timeout_ms: Int, interval_ms: Int)
}

Constructors

  • PollConfig(timeout_ms: Int, interval_ms: Int)

    Arguments

    timeout_ms

    Maximum time to wait in milliseconds.

    interval_ms

    How often to check the condition in milliseconds.

Result of a polling operation.

Either the condition was met (Ready) or we gave up (TimedOut).

Constructors

  • Ready(value): The condition was met.
  • TimedOut: The timeout elapsed.
pub type PollResult(a) {
  Ready(a)
  TimedOut
}

Constructors

  • Ready(a)

    The condition was met and returned this value.

  • TimedOut

    Timed out waiting for the condition.

Port selection strategies for test servers.

When starting test servers, you need to choose a port:

  • Port(n) - Use a specific port number
  • RandomPort - Pick a random available port (recommended)

Example

Use this anywhere you need a port selection value.

process.Port(1234)
|> should
|> be_equal(process.Port(1234))
|> or_fail_with("expected PortSelection to be constructible")
pub type PortSelection {
  Port(Int)
  RandomPort
}

Constructors

  • Port(Int)

    Use a specific port number.

  • RandomPort

    Pick a random available port in a safe range (10000-60000).

Values

pub fn await_ready(
  config config: PollConfig,
  check check: fn() -> Bool,
) -> PollResult(Bool)

Wait until a condition returns True.

Polls the check function at regular intervals until it returns True or the timeout is reached.

Use Cases

  • Waiting for a server to start accepting connections
  • Waiting for a file to appear
  • Waiting for a service to become healthy

Parameters

  • config: Poll timeout + interval.
  • check: A zero-arg function returning Bool.

Returns

Ready(True) when the check returns True, otherwise TimedOut.

Example

process.await_ready(process.quick_poll_config(), always_true)
|> should
|> be_equal(process.Ready(True))
|> or_fail_with("expected await_ready to return Ready(True)")
pub fn await_some(
  config config: PollConfig,
  check check: fn() -> Result(a, e),
) -> PollResult(a)

Wait until a function returns Ok.

Polls the check function at regular intervals until it returns Ok(value) or the timeout is reached. The value is returned in Ready(value).

Use Cases

  • Waiting for a record to appear in a database
  • Waiting for an async job to complete
  • Waiting for a resource to become available

Parameters

  • config: Poll timeout + interval.
  • check: A zero-arg function returning Result(value, error).

Returns

Ready(value) when the check returns Ok(value), otherwise TimedOut.

Example

process.await_some(process.default_poll_config(), always_ok_42)
|> should
|> be_equal(process.Ready(42))
|> or_fail_with("expected await_some to return Ready(42)")
pub fn call_actor(
  subject subject: process.Subject(msg),
  make_message make_message: fn(process.Subject(reply)) -> msg,
  timeout_ms timeout_ms: Int,
) -> reply

Call an actor and wait for a response.

This is a convenience wrapper around actor.call that makes the parameter order more ergonomic for piping.

Parameters

  • subject - The actor to call
  • make_message - Function that creates the message given a reply subject
  • timeout_ms - How long to wait for a response

Returns

The reply value from the actor.

Example

process.call_actor(todos, GetAll, 1000)
|> should
|> be_equal(["Write tests", "Run tests"])
|> or_fail_with("expected items to be preserved in insertion order")
pub fn decrement(
  counter counter: process.Subject(CounterMessage),
) -> Nil

Decrement a counter by 1.

This is an asynchronous send—it returns immediately.

Parameters

  • counter: The counter actor to decrement.

Returns

Nil. (The message is sent asynchronously.)

Example

Use this inside an it block.

let counter = process.start_counter_with(10)
process.decrement(counter)

process.get_count(counter)
|> should
|> be_equal(9)
|> or_fail_with("expected counter to be 9 after decrement")
pub fn default_poll_config() -> PollConfig

Default polling configuration.

  • 5 second timeout
  • Check every 50ms

Good for operations that might take a few seconds.

Returns

PollConfig(timeout_ms: 5000, interval_ms: 50).

Example

process.default_poll_config()
|> should
|> be_equal(process.PollConfig(timeout_ms: 5000, interval_ms: 50))
|> or_fail_with("expected default_poll_config to be 5000ms/50ms")
pub fn get_count(
  counter counter: process.Subject(CounterMessage),
) -> Int

Get the current value from a counter.

This is a synchronous call that blocks until the counter responds.

Parameters

  • counter: The counter actor to query.

Returns

The current counter value.

Example

Use this inside an it block.

let counter = process.start_counter()
process.increment(counter)

process.get_count(counter)
|> should
|> be_equal(1)
|> or_fail_with("expected counter to be 1")
pub fn increment(
  counter counter: process.Subject(CounterMessage),
) -> Nil

Increment a counter by 1.

This is an asynchronous send—it returns immediately.

Parameters

  • counter: The counter actor to increment.

Returns

Nil. (The message is sent asynchronously.)

Example

Use this inside an it block.

let counter = process.start_counter()
process.increment(counter)
process.increment(counter)

process.get_count(counter)
|> should
|> be_equal(2)
|> or_fail_with("expected counter to be 2")
pub fn quick_poll_config() -> PollConfig

Quick polling configuration.

  • 1 second timeout
  • Check every 10ms

Good for fast local operations like servers starting.

Returns

PollConfig(timeout_ms: 1000, interval_ms: 10).

Example

process.quick_poll_config()
|> should
|> be_equal(process.PollConfig(timeout_ms: 1000, interval_ms: 10))
|> or_fail_with("expected quick_poll_config to be 1000ms/10ms")
pub fn set_count(
  counter counter: process.Subject(CounterMessage),
  value value: Int,
) -> Nil

Set a counter to a specific value.

This is an asynchronous send—it returns immediately.

Parameters

  • counter: The counter actor to set.
  • value: The new value to set.

Returns

Nil. (The message is sent asynchronously.)

Example

Use this inside an it block.

let counter = process.start_counter()
process.set_count(counter, 42)

process.get_count(counter)
|> should
|> be_equal(42)
|> or_fail_with("expected counter to be 42 after set_count")
pub fn start_actor(
  initial_state initial_state: state,
  handler handler: fn(state, msg) -> actor.Next(state, msg),
) -> process.Subject(msg)

Start a generic actor with custom state and message handler.

The actor is linked to the test process and will be automatically cleaned up when the test ends.

Parameters

  • initial_state - The actor’s starting state
  • handler - Function fn(state, message) -> actor.Next(state, message)

Returns

A Subject(msg) you can send messages to (or call with call_actor).

Example

let todos = process.start_actor([], handle_todo_message)

erlang_process.send(todos, Add("Write tests"))
erlang_process.send(todos, Add("Run tests"))

process.call_actor(todos, GetAll, 1000)
|> should
|> be_equal(["Write tests", "Run tests"])
|> or_fail_with("expected items to be preserved in insertion order")
pub fn start_counter() -> process.Subject(CounterMessage)

Start a counter actor initialized to 0.

The counter is linked to the test process and will be automatically cleaned up when the test ends.

Returns

A Subject(CounterMessage) you can pass to the other counter helpers (or send messages to directly).

Example

Use this inside an it block.

let counter = process.start_counter()
process.increment(counter)
process.increment(counter)

process.get_count(counter)
|> should
|> be_equal(2)
|> or_fail_with("expected counter to be 2")
pub fn start_counter_with(
  initial initial: Int,
) -> process.Subject(CounterMessage)

Start a counter actor with a specific initial value.

Parameters

  • initial: The initial count value.

Returns

A Subject(CounterMessage) for the started counter.

Example

Use this inside an it block.

let counter = process.start_counter_with(10)
process.decrement(counter)

process.get_count(counter)
|> should
|> be_equal(9)
|> or_fail_with("expected counter to be 9 after decrement")
pub fn unique_port() -> Int

Generate a unique port number for test servers.

Returns a random port between 10,000 and 60,000. This range avoids:

  • Well-known ports (0-1023)
  • Registered ports commonly used by services (1024-9999)
  • Ports near the upper limit that some systems reserve

Using random ports prevents conflicts when running tests in parallel.

Example

Use this in test setup, before starting a server.

process.unique_port()
|> should
|> be_between(10_000, 60_000)
|> or_fail_with("expected unique_port to be within 10k..60k")
Search Document