Behavior-driven
development for Raku.
A complete behavior-driven testing framework for Raku, with expressive spec syntax, rich matchers, first-class mocks, built-in coverage, and pluggable output formatters. Specs that read like prose and run like tests.
Run your suite with the included behave CLI, focus on a single example with tags,
bisect flaky specs down to the offending interaction, and benchmark hot paths without leaving the framework.
Tight feedback loops, readable failure output, and tests that double as living documentation for the rest of your team.
Every piece you need, in one library.
From the spec syntax to the runner, from mocks to coverage, Behave ships the whole stack so you can skip the meta-work and write specs.
Expressive syntax
describe, context, it, let, before-each. The vocabulary you already know, ported to Raku idioms.
Rich matchers
Smart-match, eq, include, match, raise-error, change.by, be-within, async. All composable with .and / .or.
Mocks, stubs & spies
double() for ad-hoc test doubles, allow() for partial stubs, spy() to record calls. All auto-cleaned between examples.
Output formatters
Tree, Progress, Documentation, HTML, JSON, JUnit XML, TAP. Same run, every output your tooling speaks.
Compare formatters →Built-in coverage
Line coverage out of the box. No extra modules, no plugins, no ceremony. Just --coverage.
Doc extraction
Generate human-readable documentation directly from your spec tree. Descriptions become headings, examples become prose.
Docs from specs →Shared contexts & examples
Factor common setup into shared-context. Share example suites across implementations with shared-examples.
Timing & benchmarks
Per-example timing, slow-test warnings, memory profiling, and statistical benchmarks with regression detection, all first-class.
Profile & bench →Specs that read like the conversation you had about the bug.
Group related expectations with describe and context.
Lazy-evaluate fixtures with let. Bind the subject under test with subject.
Behave's spec syntax maps the way you actually think about behavior to Raku without losing
the language's syntactic personality.
- Lazy & memoized lets. Values are computed once per example, reset between examples.
- Composable matchers. Chain
.and,.or,.not, with short-circuit semantics built in. - Auto-cleaned mocks. Every stub installed in an example is uninstalled when the example ends.
- Junctions, ranges, regex. All the smart-match power Raku already gives you.
use BDD::Behave;
describe 'String#flip', {
let(:word, { 'hello world' });
it 'reverses the characters', {
expect(:word.flip).to.be('dlrow olleh');
}
it 'is its own inverse', {
expect(:word.flip.flip).to.be(:word);
}
context 'on the empty string', {
let(:word, { '' });
it 'returns the empty string', {
expect(:word.flip).to.be('');
}
}
}
Rich matchers, one consistent grammar.
Behave's matcher role is small and the built-ins are deep. Numeric tolerance,
collection membership, attribute checks, regex, async streams, promises,
exceptions, all expressed through the same expect(...).to.matcher(...)
chain, with first-class .not and structural failure metadata.
- Numeric.
be-greater-than,be-between(1, 10).exclusive,be-within(0.1).of(5.0) - Collections.
include,contain-exactly,start-with,all(Int) - Async.
be-kept,complete-within(2),emit(1, 2, 3),eventually - Custom. Define matchers with
define-matcheror compose existing ones.
expect(5.05).to.be-within(0.1).of(5.0);
expect([1, 2, 3]).to.contain-exactly(3, 1, 2);
expect([1, 2, 3]).to.all(Int);
expect('hello world').to.match(/world/);
expect({ die "boom" }).to.raise-error(X::AdHoc, /boom/);
my $counter = 0;
expect({ $counter += 5 }).to.change({ $counter })
.from(0).to(5).by(5);
expect(start { compute() }).to.complete-within(1);
expect(:user.age).to.be-greater-than(17)
.and.be-less-than(65);
Stand-ins that don't leak between examples.
double(SomeClass) produces a verifying double that rejects stubs for
methods the class doesn't have. allow($obj).to.receive('m') stubs a single
method on a real instance. Every other method keeps its real behavior, perfect
for partial mocks. spy($real) records every call without changing what
the object does. Behave uninstalls everything at end-of-example, so the next
example sees the world as it was.
describe 'partial mock', {
it 'stubs label but lets deposit run for real', {
my $a = Account.new(balance => 100);
allow($a).to.receive('label').and-return('STUB');
expect($a.label).to.be('STUB'); # stubbed
expect($a.deposit(50)).to.be(150); # real method
expect($a.balance).to.be(150); # real state
}
it 'records calls on a spy', {
my $log = double('Logger');
$log.info('starting');
$log.warn('careful', :reason);
expect($log).to.have-received('info').once;
expect($log).to.have-received('warn')
.with(anything, :reason);
}
}
Thirty seconds to first green.
One command to install. One specs/ directory to create. behave finds and runs every *spec.raku it sees.