A vocabulary for behavior.
Behave gives you the building blocks BDD has always used
(describe, it, let, before-each)
adapted to Raku's syntax. Specs are ordinary Raku files, and these
primitives are ordinary exported subs.
#describe / context
describe and context define a group of related examples.
They're aliases. Pick whichever reads better in plain English. Groups can be
nested arbitrarily, and Raku scoping rules apply inside each block, so a
my $foo declared in a group is visible to its examples and nested groups.
use BDD::Behave;
describe 'User#full-name', {
context 'when both names are present', {
it 'joins them with a space', {
my $u = User.new(:first, :last);
expect($u.full-name).to.be('Ada Lovelace');
}
}
context 'when only the first name is present', {
it 'returns the first name alone', {
my $u = User.new(:first);
expect($u.full-name).to.be('Ada');
}
}
}
context reads naturally for branching scenarios
("when X", "with Y"). Use describe for the thing under test
("User#full-name", "the parser").
#it
Each it is an example: a single behavior the surrounding group asserts.
Pass a description and a block. Auto-derived descriptions are also supported:
omit the string and Behave infers it from the matcher.
describe 'String#uc', {
it 'uppercases ASCII', {
expect('hi'.uc).to.be('HI');
}
# auto-described, description derived from the expectation
it { expect('abc'.uc).to.be('ABC') }
# pending, body is not executed, marked with a reason
pending 'works on accented characters', 'pending Unicode fold work', {
expect('café'.uc).to.be('CAFÉ');
}
}
#let / let-bang
let defines a value that is lazy (only computed when
first read) and memoized per example (reset between examples). Use
it for the test subject and any inputs whose construction you'd otherwise repeat.
describe 'cart totals', {
let(:cart, { ShoppingCart.new });
let(:item, { Item.new(:price(9.99)) });
it 'is zero when empty', {
expect(:cart.total).to.be(0);
}
it 'adds the item price', {
:cart.add(:item);
expect(:cart.total).to.be(9.99);
}
context 'with a coupon', {
# inner let shadows the outer one
let(:cart, { ShoppingCart.new(:coupon('10OFF')) });
it 'applies the discount', {
:cart.add(:item);
expect(:cart.total).to.be(8.99);
}
}
}
let-bang is the Raku-friendly spelling of RSpec's let!. It
defines the same memoized value, but forces it before every example body so any
side effects in the block run whether or not the example reads the value. Useful
when the creation is itself the setup (inserting a row, recording a fixture).
describe 'invoices index', {
let-bang(:invoice, { Invoice.create(:amount(100)) });
it 'returns the invoice', {
# passes even though we never read :invoice, the row exists
expect(Invoice.all.elems).to.be(1);
}
}
#subject / subject-bang
subject is a specialized let: the thing this group is
testing. Use the bare .is / .is-not matchers for one-liners,
or refer to :subject by name.
describe 'Account', {
subject { Account.new(:balance(100)) }
it 'starts with the configured balance', {
expect(:subject.balance).to.be(100);
}
it { is.an-instance-of(Account) }
}
#hooks
Hooks run on a predictable schedule around every example or group.
| Hook | Runs |
|---|---|
| before-each | Before each example in the group (and nested groups). |
| before-all | Once before the first example in the group. |
| after-each | After each example, even on failure. |
| after-all | Once after the last example in the group. |
| around-each | Wraps each example body. Must invoke the continuation to actually run it. |
| around-all | Wraps the entire group. |
describe 'database fixtures', {
before-all { DB.connect }
after-all { DB.disconnect }
before-each { DB.begin-transaction }
after-each { DB.rollback }
around-each -> &example {
my $t0 = now;
example();
note "took { now - $t0 }s";
}
it 'inserts rows', { User.create(:name); expect(User.all.elems).to.be(1) }
it 'cleans up between examples', { expect(User.all.elems).to.be(0) }
}
before-each, let-bang, and
any other registered hooks run in declaration order. No surprises.
#tags, focus & skip
Tag examples and groups for arbitrary filtering on the command line.
fit / fdescribe focus the run on a single example or group.
xit / xdescribe skip them.
describe 'integration', :tag, {
it 'talks to the database', :tag, {
expect(DB.ping).to.be(True);
}
fit 'this one only, for now', {
expect(1 + 1).to.be(2);
}
xit 'temporarily off', {
expect(...).to.be(...);
}
}
# run only :slow examples
behave --tag slow specs/
# exclude :integration examples
behave --tag '~integration' specs/
# example filter by description regex
behave --example 'full-name' specs/users-spec.raku