behave
behave is a behavior-driven testing library for Rust. It gives you a behave!
macro for nested, readable test suites and an expect! API for expressive
assertions, while still compiling down to ordinary #[test] functions.
What It Is
Use behave when you want test code that reads like scenarios instead of a flat
list of unrelated unit tests:
- nested groups instead of large test modules
setupblocks that flow into child scenarioseachblocks for parameterized/table-driven testsmatrixblocks for Cartesian product test generationxfailfor expected-failure teststagmetadata for grouping and filtering testsskip_when!for runtime conditional skipping- built-in matchers for equality, strings, collections, options, results, and floats
pendingandfocusmarkers for test workflow- optional
cargo-behaveCLI with tree/JSON/JUnit output, watch mode, tag filtering, and flaky-test detection
How It Works
behave! is a proc macro. At compile time it turns your scenario tree into
standard Rust test functions, so cargo test still runs the suite and there is
no custom runtime to keep alive.
Start Fast
Add the crate as a dev-dependency:
Create tests/behave_smoke.rs:
use *;
behave!
Run it:
That is the whole onboarding path. The generated tests are normal #[test]
items, so you can keep using the usual Rust tooling around them.
Parameterized Tests
Use each to generate one test per case. Each case becomes its own #[test]
function, so failures tell you exactly which input broke:
use *;
behave!
This generates addition::case_0, addition::case_1, and addition::case_2.
Single-parameter cases work too:
use *;
behave!
each inherits setup, teardown, and tokio; from the parent group:
use *;
behave!
See examples/parameterized.rs for the full
working example.
Setup Inheritance
setup bindings flow from parent groups into child scenarios and child
setup blocks. This avoids duplicating shared state:
use *;
behave!
See examples/setup_inheritance.rs for
a fuller version with helper functions and shadowing.
Teardown
teardown blocks run after every test in the group, even if the test panics (sync tests) or returns an error (async tests). Use them for cleanup:
use *;
behave!
Inner teardowns run before outer teardowns (like destructors). See
examples/teardown.rs for nested teardown patterns.
Copy-Paste Commands
Create a new project and try behave in one go:
Then put the Quick Start example above into tests/behave_smoke.rs and run
cargo test.
Install the optional CLI:
Run the suite with tree output:
Run only tests tagged slow:
Watch for file changes and re-run:
Emit a machine-readable report:
Features
| Feature | Default | Description |
|---|---|---|
std |
Yes | Standard library support |
cli |
No | Enables cargo-behave and flaky-test utilities |
color |
No | ANSI-colored diff output for assertion failures |
regex |
No | to_match_regex and to_contain_regex matchers |
tokio |
No | Enables tokio; async test generation |
Macros
| Macro | Description | Docs |
|---|---|---|
behave! |
BDD test suite DSL with groups, setup, teardown, each, matrix, tags, and more | Reference |
expect! |
Wrap a value for matcher assertions with structured error messages | Reference |
expect_panic! |
Assert that an expression panics | Reference |
expect_no_panic! |
Assert that an expression does not panic | Reference |
skip_when! |
Conditionally skip a test at runtime with a reason | Reference |
Matchers
| Category | Matchers |
|---|---|
| Equality | to_equal, to_not_equal |
| Boolean | to_be_true, to_be_false |
| Ordering | to_be_greater_than, to_be_less_than, to_be_at_least, to_be_at_most |
| Option | to_be_some, to_be_none, to_be_some_with |
| Result | to_be_ok, to_be_err, to_be_ok_with, to_be_err_with |
| Collections | to_contain, to_be_empty, to_not_be_empty, to_have_length, to_contain_all_of |
| Strings | to_start_with, to_end_with, to_contain_substr, to_have_str_length |
| Float | to_approximately_equal, to_approximately_equal_within |
| Panic | expect_panic!, expect_no_panic! |
| Predicate | to_satisfy |
| Custom | to_match with BehaveMatch |
| Regex (feature) | to_match_regex, to_contain_regex |
Map (HashMap, BTreeMap) |
to_contain_key, to_contain_value, to_contain_entry, to_be_empty, to_not_be_empty, to_have_length |
| Composition | all_of, any_of, not_matching |
All matchers respect .not() / .negate().
The matcher docs live in docs/matchers/ with one page per matcher (plus a quick index).
Real Examples
| Example | What it shows |
|---|---|
examples/quickstart.rs |
Recommended first suite with setup, matchers, and pending |
examples/parameterized.rs |
each blocks with multi-param tuples, single params, and inherited setup |
examples/setup_inheritance.rs |
Three levels of nested setup with a realistic pricing domain |
examples/teardown.rs |
Panic-safe cleanup, nested teardowns, and resource management |
examples/custom_matcher.rs |
Reusable BehaveMatch<T> matcher type with negation |
tests/smoke.rs |
Full DSL and matcher surface coverage |
What You Can And Cannot Do
You can:
- nest groups freely
- share bindings from a parent
setupinto child scenarios - shadow a setup binding with a later
letin a childsetupor scenario body - use
eachblocks for parameterized/table-driven test generation - use
teardownblocks for cleanup after each test (panic-safe in sync, error-safe in async) - declare
tokio;in a group to generate#[tokio::test]async tests (requirestokiofeature) - use
cargo testnormally because generated tests are ordinary#[test]functions - use
cargo behavefor tree output, filters, and libtest flags - use
cargo behave --output jsonorcargo behave --output junitfor CI-friendly reports - use
cargo behave --manifest-path path/to/Cargo.tomlor--package namein workspaces - use
cargo behave --tag slowto run only tagged tests,--exclude-tag flakyto exclude - use
cargo behave --focusto run only focused tests - use
cargo behave --fail-on-focusto reject focused tests in CI - use
cargo behave --watchto re-run on file changes - use
skip_when!(condition, "reason")to conditionally skip tests at runtime
Current limitations:
- one
setupblock per group, oneteardownblock per group - DSL order within a group:
tokio;→timeout→setup {}→teardown {}→ children pendingblocks must be empty- async teardown is error-safe but not panic-safe (no
catch_unwindacross.awaitpoints)
Why Rely On It
The current trust signals are intentionally concrete:
behave!compiles to ordinary#[test]functions- runnable examples live in
examples/and are exercised in tests - public docs, doctests, Clippy, and rustdoc warnings are checked together
unsafeis forbidden by lint configuration- limitations are documented explicitly instead of left implicit
- security reporting is documented in SECURITY.md
For the fuller trust and maintenance picture, see docs/RELIABILITY.md.
Flaky Test Detection
Create behave.toml in your project root:
[]
= true
= ".behave/history.json"
= 5
When enabled, cargo behave records past outcomes and warns when a test fails
after many consecutive passes without source changes in the selected package set.
Add .behave/ to .gitignore.
Documentation
- User Guide
- Macro Reference —
behave!|expect!|expect_panic!|expect_no_panic!|skip_when! - Matcher Reference
- CLI Guide
- Reliability
- Architecture
- Contributing
- API docs on docs.rs
Security
See SECURITY.md for the reporting process.
License
Licensed under the Apache License, Version 2.0. See LICENSE.