configuration
Configuration
Behave loads two Raku-based configuration files at startup and merges them with your CLI flags. This is the standard way to keep team-wide and machine-wide defaults out of every spec invocation.
File format
A .behave file is a Raku script. It calls configure-behave with a block that receives a Configuration object:
use BDD::Behave::Configuration;
configure-behave -> $config {
$config.format = 'documentation';
$config.order = 'random';
$config.seed = 12345;
$config.include-tag('focus');
$config.exclude-tag('slow');
$config.aggregate-failures = True;
$config.fail-fast = 1;
}Because the file is plain Raku, you can pull in helpers (use ...;) and compute values, but each configure-behave block should be self-contained: anything you set inside the block is stored on $config for the run.
Precedence
When more than one source sets the same option, the highest-precedence source wins:
CLI flags (highest): explicit
--format,--order,--tag, etc.Project config:
.behavein the current working directory.User config:
~/.behavein the user's home directory.Built-in defaults (lowest): what Behave uses when nothing else is set.
List-style options (include-tag, exclude-tag, example-pattern, only-location, spec-paths, include, hooks) accumulate across all four layers: a tag listed in ~/.behave and another listed on the CLI both apply. Scalar options like format, order, seed, fail-fast, verbose, and aggregate-failures override: the highest-precedence layer that sets them wins.
Disabling configuration
Three CLI flags and one environment variable bypass config loading:
| Flag / env | Effect |
|---|---|
--config PATH | Load PATH as the only config, skipping ~/.behave and ./.behave. |
--no-config | Skip both default config files entirely. |
--no-user-config | Skip ~/.behave, but still read ./.behave. |
--no-project-config | Skip ./.behave, but still read ~/.behave. |
BEHAVE_DISABLE_CONFIG=1 | Same as --no-config. Useful for CI and subprocess testing. |
--config PATH errors with exit code 2 if PATH does not exist.
Settings
Every scalar attribute corresponds to its CLI counterpart:
| Configuration attribute | CLI flag | Notes |
|---|---|---|
$config.format | --format | A registered formatter name (e.g. 'progress'). |
$config.order | --order | 'random' or 'defined'. |
$config.seed | --seed | Int, only meaningful for 'random'. |
$config.fail-fast | --fail-fast[=N] | 0 disables, N >= 1 stops after N failures. |
$config.verbose | --verbose | Bool. |
$config.aggregate-failures | --aggregate-failures[=LABEL] | Bool or Str label. |
$config.profile-limit | --profile[=N] | 0 disables, N shows top-N slow examples. |
$config.slow-threshold | --slow-threshold | Real seconds. |
$config.benchmark-mode | --benchmark | Bool. |
$config.benchmark-iterations | --benchmark-iterations | Positive Int. |
$config.benchmark-baseline | --benchmark-baseline | IO::Path. |
$config.benchmark-save | --benchmark-save | IO::Path. |
$config.benchmark-threshold | --benchmark-threshold | Real fraction (0.10 = 10%). |
$config.benchmark-format | --benchmark-format | 'text' or 'json'. |
$config.benchmark-output | --benchmark-output | IO::Path. |
$config.parallel | --parallel N | Int >= 1 enables parallel execution with N worker subprocesses. Absence (or value 0) keeps single-process serial execution. CLI overrides config. |
$config.parallel-mode | --parallel-mode | 'isolated' (default), 'lpt', or 'queue'. See Parallel Execution. |
$config.parallel-retry | --parallel-retry | Non-negative Int. Per-shard retry budget when a worker crashes (exit > 1). See Per-shard retry. |
$config.seed-mode | --seed-mode | 'xor' (default) or 'stable'. |
$config.show-seed | --show-seed | Bool. Print the seed even on a passing run (default prints it only on failure). |
$config.progress-total | --progress-total | Bool. Append a (N/TOTAL) counter to each progress char under --parallel. |
The list-style mutators are repeatable:
configure-behave -> $config {
$config.include-tag('focus', 'wip');
$config.exclude-tag('slow');
$config.example-pattern('checkout flow');
$config.only-location('specs/users-spec.raku:42');
$config.include-spec('specs/');
}include-spec populates the default list of spec paths used when no paths are passed on the CLI.
Helper inclusion
$config.include(SomeClass) instantiates SomeClass once for the run and exposes it inside every example via the $*BEHAVE-HELPERS dynamic variable, keyed by the class's short name:
class APIHelpers {
method as-user($name) { ... }
method json-post(...) { ... }
}
configure-behave -> $config {
$config.include(APIHelpers);
}Inside a spec:
it 'creates a new order', {
my $resp = $*BEHAVE-HELPERS<APIHelpers>.as-user('alice');
expect($resp.status).to.eq(201);
}Use :as<key> to choose a custom key, which is helpful when class names clash or you want a shorter accessor:
$config.include(APIHelpers, :as<api>);
# $*BEHAVE-HELPERS<api>.as-user('alice')One instance is created per class per run (cached in the runner). State stored on a helper persists across all examples in the suite, so use it for fixtures and shared connections, not per-example state.
Calling helper methods on the topic
A helper's methods are also callable directly on the example topic. When an example block takes the topic (-> $_ { ... }), an unknown method on it resolves first to a let of that name, then to a method on any included helper:
class APIHelpers {
method current-user() { ... }
}
configure-behave -> $config {
$config.include(APIHelpers);
}
it 'reads the signed-in user', -> $_ {
expect(.current-user).to.eq('alice');
}A let always shadows a helper method of the same name, so a spec can override helper-provided values locally. Arguments pass straight through: .json-post($payload) calls the helper method with $payload.
Global hooks
Per-run hooks let you set up shared infrastructure without repeating it in every spec. Each phase composes with the per-group hooks declared inside spec files:
| Phase | Fires |
|---|---|
before-all | Once per suite, before any example runs. |
after-all | Once per suite, after every example has run. |
before-each | Before every example, before any group before-each. |
after-each | After every example, after any group after-each. |
around-each | Wraps every example, outside the group around-each. |
configure-behave -> $config {
$config.before-all({ DB.migrate });
$config.after-all({ DB.disconnect });
$config.before-each({ DB.start-transaction });
$config.after-each({ DB.rollback });
$config.around-each(-> &next {
my $start = now;
next();
note "example took {now - $start}s";
});
}Per-example phases (before-each, after-each, around-each) accept tag filters via named arguments:
$config.before-each({ Browser.launch }, :tag<feature>);
$config.after-each({ Browser.close }, :tag<feature>);Any unrecognised named argument is treated as a metadata filter: the hook only fires for examples whose effective metadata matches.
A global hook that throws is caught and reported as a warning so the run can continue.
Metadata filtering
$config.filter(:key<value>) keeps only the examples whose effective metadata (declared via :key<value> on describe, context, or it and inherited down the tree) matches. Pass a Bool true value ($config.filter(:db)) to require the key to be present and truthy.
configure-behave -> $config {
$config.filter(:type<unit>); # only :type<unit> examples
$config.filter(:db); # only examples with :db
$config.exclude-filter(:flaky); # skip every :flaky example
}Multiple filter calls combine with AND: each pair must match. exclude-filter does the opposite: any matching pair drops the example. Config-level filters also AND with --tag/--exclude-tag/--example on the CLI, and with focus mode.
filter-run-when-matching
Inspired by RSpec's config.filter_run_when_matching :focus, this is a "soft" filter: it applies when at least one example matches, and is silently dropped otherwise so the rest of the run proceeds normally. Useful for a focus workflow: when you mark an example :focus, only it runs. When you remove the mark, the whole suite runs.
configure-behave -> $config {
$config.filter-run-when-matching(:focus);
$config.filter-run-when-matching(:wip); # additional filter (AND with focus)
}You can pass either a string key (treated as "metadata is truthy") or a pair (key => value). Multiple registrations stack with AND semantics, but each one is independently checked for the no-match short-circuit: a registration that matches nothing is dropped without affecting the others.
filter-run-when-matching interacts with the other filters:
| Other filter active | Behavior |
|---|---|
| None | The filter applies if any example matches. Otherwise the entire suite runs. |
--tag, --exclude-tag, --example | The filter is checked against the full spec tree. If it matches, it is added to the AND set with the CLI filters. |
$config.filter (hard) | Hard filter applies first. If filter-run-when-matching matches under that constraint, both apply. |
Example: complete .behave
use BDD::Behave::Configuration;
class APIHelpers {
method as-user($name) { ... }
}
configure-behave -> $config {
# Defaults
$config.format = 'documentation';
$config.order = 'random';
$config.fail-fast = 1;
$config.profile-limit = 10;
# Default spec paths (used when none are passed on the CLI)
$config.include-spec('specs/');
# Helpers
$config.include(APIHelpers);
# Global setup / teardown
$config.before-all({ DB.migrate });
$config.after-all({ DB.disconnect });
# Filters
$config.exclude-filter(:flaky);
# Honor a :focus marker when present
$config.filter-run-when-matching(:focus);
}With this file in your project root, behave (no arguments) loads it, applies your CLI flags on top, and runs specs/ in documentation format with full focus / flaky / DB management, no flags required.