dream_test/gherkin/world
Scenario state management for Gherkin tests.
Each scenario gets its own World instance for storing state between steps. The World is automatically created before a scenario runs and cleaned up after.
How It Works
The World uses ETS (Erlang Term Storage) for mutable storage with process isolation. Each scenario gets a unique table that’s cleaned up automatically.
Example
import dream_test/gherkin/feature.{feature, given, scenario, then, when}
import dream_test/gherkin/steps.{type StepContext, get_float, step}
import dream_test/gherkin/world.{get_or, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
import gleam/result
// NOTE: We annotate `StepContext` because record field access needs a known type.
fn step_have_balance(context: StepContext) {
// {float} captures the numeric value (even with $ prefix)
let balance = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", balance)
Ok(succeed())
}
fn step_withdraw(context: StepContext) {
let current = get_or(context.world, "balance", 0.0)
let amount = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", current -. amount)
Ok(succeed())
}
fn step_balance_is(context: StepContext) {
let expected = get_float(context.captures, 0) |> result.unwrap(0.0)
get_or(context.world, "balance", 0.0)
|> should
|> be_equal(expected)
|> or_fail_with("Balance mismatch")
}
pub fn register(registry) {
registry
|> step("I have a balance of ${float}", step_have_balance)
|> step("I withdraw ${float}", step_withdraw)
|> step("my balance should be ${float}", step_balance_is)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("Bank Account", steps, [
scenario("Withdrawal", [
given("I have a balance of $100.00"),
when("I withdraw $30.00"),
then("my balance should be $70.00"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
Types
Values
pub fn cleanup(world world: World) -> Nil
Clean up the World after a scenario completes.
Called automatically by the runner after each scenario. Deletes the ETS table and frees resources.
Parameters
world: The World to clean up
Example
import dream_test/gherkin/world
pub fn main() {
// In normal gherkin runs, the runner creates and cleans up the World for you.
let w = world.new_world("example_scenario")
world.cleanup(w)
}
pub fn delete(world world: World, key key: String) -> Nil
Delete a key from the World.
Removes a key-value pair. If the key doesn’t exist, this is a no-op.
Parameters
world: The World to modifykey: String key to delete
Example
import dream_test/gherkin/feature.{feature, given, scenario, then, when}
import dream_test/gherkin/steps.{type StepContext, step}
import dream_test/gherkin/world.{delete, has, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
fn step_store(context: StepContext) {
put(context.world, "temp", True)
Ok(succeed())
}
fn step_delete(context: StepContext) {
delete(context.world, "temp")
Ok(succeed())
}
fn step_is_absent(context: StepContext) {
has(context.world, "temp")
|> should
|> be_equal(False)
|> or_fail_with("expected temp to be absent")
}
pub fn register(registry) {
registry
|> step("temp is stored", step_store)
|> step("temp is deleted", step_delete)
|> step("temp should be absent", step_is_absent)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("World: has + delete", steps, [
scenario("Deleting a key", [
given("temp is stored"),
when("temp is deleted"),
then("temp should be absent"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
pub fn get(
world world: World,
key key: String,
) -> Result(a, String)
Retrieve a value from the World.
Returns Ok(value) if the key exists, Error(message) if not found.
The caller is responsible for ensuring type consistency.
Parameters
world: The World to querykey: String key to look up
Returns
Ok(value): Key exists, returns the stored valueError(String): Key doesn’t exist (human-readable message)
Example
import dream_test/gherkin/feature.{feature, given, scenario, then}
import dream_test/gherkin/steps.{type StepContext, step}
import dream_test/gherkin/world.{get, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
fn step_store(context: StepContext) {
put(context.world, "count", 42)
Ok(succeed())
}
fn step_count_is_42(context: StepContext) {
case get(context.world, "count") {
Ok(count) ->
count
|> should
|> be_equal(42)
|> or_fail_with("count mismatch")
Error(message) -> Error(message)
}
}
pub fn register(registry) {
registry
|> step("count is stored", step_store)
|> step("count should be 42", step_count_is_42)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("World: get", steps, [
scenario("Reading a stored value", [
given("count is stored"),
then("count should be 42"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
pub fn get_or(
world world: World,
key key: String,
default default: a,
) -> a
Retrieve a value or return a default.
Convenience function that returns the stored value if the key exists, or the provided default if not found.
Parameters
world: The World to querykey: String key to look updefault: Default value to return if key not found
Example
import dream_test/gherkin/feature.{feature, given, scenario, then, when}
import dream_test/gherkin/steps.{type StepContext, get_float, step}
import dream_test/gherkin/world.{get_or, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
import gleam/result
// NOTE: We annotate `StepContext` because record field access needs a known type.
fn step_have_balance(context: StepContext) {
// {float} captures the numeric value (even with $ prefix)
let balance = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", balance)
Ok(succeed())
}
fn step_withdraw(context: StepContext) {
let current = get_or(context.world, "balance", 0.0)
let amount = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", current -. amount)
Ok(succeed())
}
fn step_balance_is(context: StepContext) {
let expected = get_float(context.captures, 0) |> result.unwrap(0.0)
get_or(context.world, "balance", 0.0)
|> should
|> be_equal(expected)
|> or_fail_with("Balance mismatch")
}
pub fn register(registry) {
registry
|> step("I have a balance of ${float}", step_have_balance)
|> step("I withdraw ${float}", step_withdraw)
|> step("my balance should be ${float}", step_balance_is)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("Bank Account", steps, [
scenario("Withdrawal", [
given("I have a balance of $100.00"),
when("I withdraw $30.00"),
then("my balance should be $70.00"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
pub fn has(world world: World, key key: String) -> Bool
Check if a key exists in the World.
Returns True if the key exists, False otherwise.
Parameters
world: The World to querykey: String key to check
Example
import dream_test/gherkin/feature.{feature, given, scenario, then, when}
import dream_test/gherkin/steps.{type StepContext, step}
import dream_test/gherkin/world.{delete, has, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
fn step_store(context: StepContext) {
put(context.world, "temp", True)
Ok(succeed())
}
fn step_delete(context: StepContext) {
delete(context.world, "temp")
Ok(succeed())
}
fn step_is_absent(context: StepContext) {
has(context.world, "temp")
|> should
|> be_equal(False)
|> or_fail_with("expected temp to be absent")
}
pub fn register(registry) {
registry
|> step("temp is stored", step_store)
|> step("temp is deleted", step_delete)
|> step("temp should be absent", step_is_absent)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("World: has + delete", steps, [
scenario("Deleting a key", [
given("temp is stored"),
when("temp is deleted"),
then("temp should be absent"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
pub fn new_world(scenario_id scenario_id: String) -> World
Create a new World for a scenario.
Called automatically by the runner before each scenario. Each World gets a unique ETS table for isolated storage.
Parameters
scenario_id: Unique identifier for the scenario
Example
import dream_test/gherkin/world
pub fn main() {
// In normal gherkin runs, the runner creates and cleans up the World for you.
let w = world.new_world("example_scenario")
world.cleanup(w)
}
pub fn put(
world world: World,
key key: String,
value value: a,
) -> Nil
Store a value in the World.
Stores any value by string key. If the key already exists, the value is replaced.
Parameters
world: The World to store inkey: String key for the valuevalue: Any value to store
Example
import dream_test/gherkin/feature.{feature, given, scenario, then, when}
import dream_test/gherkin/steps.{type StepContext, get_float, step}
import dream_test/gherkin/world.{get_or, put}
import dream_test/matchers.{be_equal, or_fail_with, should, succeed}
import dream_test/reporters/bdd
import dream_test/reporters/progress
import dream_test/runner
import gleam/io
import gleam/result
// NOTE: We annotate `StepContext` because record field access needs a known type.
fn step_have_balance(context: StepContext) {
// {float} captures the numeric value (even with $ prefix)
let balance = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", balance)
Ok(succeed())
}
fn step_withdraw(context: StepContext) {
let current = get_or(context.world, "balance", 0.0)
let amount = get_float(context.captures, 0) |> result.unwrap(0.0)
put(context.world, "balance", current -. amount)
Ok(succeed())
}
fn step_balance_is(context: StepContext) {
let expected = get_float(context.captures, 0) |> result.unwrap(0.0)
get_or(context.world, "balance", 0.0)
|> should
|> be_equal(expected)
|> or_fail_with("Balance mismatch")
}
pub fn register(registry) {
registry
|> step("I have a balance of ${float}", step_have_balance)
|> step("I withdraw ${float}", step_withdraw)
|> step("my balance should be ${float}", step_balance_is)
}
pub fn tests() {
let steps = steps.new() |> register()
feature("Bank Account", steps, [
scenario("Withdrawal", [
given("I have a balance of $100.00"),
when("I withdraw $30.00"),
then("my balance should be $70.00"),
]),
])
}
pub fn main() {
runner.new([tests()])
|> runner.progress_reporter(progress.new())
|> runner.results_reporters([bdd.new()])
|> runner.exit_on_failure()
|> runner.run()
}
pub fn scenario_id(world world: World) -> String
Get the scenario ID for this World.
Useful for debugging and logging.
Parameters
world: The World to query
Returns
The scenario ID string.
Example
import dream_test/gherkin/world
pub fn main() {
let w = world.new_world("example_scenario")
let id = world.scenario_id(w)
world.cleanup(w)
id
}