subject
subject
subject defines the primary value under test in a describe or context block. It is implemented as a let under the name :subject, so all of let's lazy-and-memoized semantics apply.
Use subject when you want to name "the thing this group is about" once and refer to it across many examples.
Defining a subject
Three forms are accepted:
subject({ Widget.new(:bar(99)) }); # anonymous, registered as :subject
subject(:widget, { Widget.new(:bar(99)) }); # named, also aliased as :subject
subject('widget', { Widget.new(:bar(99)) }); # string-name form, also aliased as :subjectWhen a name is given, the value is reachable through both the given name and :subject. They share a single memoized evaluation per example.
Reading the subject
There are three ways to read the subject:
1. The subject() reader
Inside an example, calling subject() with no arguments returns the memoized value:
describe 'Widget', {
subject({ Widget.new(:bar(99)) });
it 'has the right bar', {
expect(subject().bar).to.be(99);
}
}subject() outside an example body raises an error.
2. Named-argument form in expect
describe 'Widget', {
subject({ Widget.new(:bar(99)) });
it 'is a Widget', {
expect(:subject).to.be(Widget);
}
}3. Through the given name when subject(:name, ...) is used
describe 'Widget', {
subject(:widget, { Widget.new(:bar(99)) });
it 'is reachable as :widget', {
expect(:widget).to.be(Widget);
}
it 'and as :subject', {
expect(:subject).to.be(Widget);
}
}Memoization
subject is lazy: the block runs the first time it's read in an example, and the value is cached for subsequent reads in the same example. Between examples the cache is reset.
my $hits = 0;
describe 'memo', {
subject({ ++$hits });
it 'evaluates once', {
subject(); subject(); subject();
expect($hits).to.be(1);
}
}Shadowing
Inner subject definitions shadow outer ones for examples in the inner group:
describe 'outer', {
subject({ 'outer' });
it 'sees outer', { expect(:subject).to.be('outer') }
context 'inner', {
subject({ 'inner' });
it 'sees inner', { expect(:subject).to.be('inner') }
}
}subject-bang (eager evaluation)
subject-bang is the Raku-friendly spelling of RSpec's subject!. It defines a subject like subject does, but the block is forced before every example body so any side effects in the block run whether or not the example reads the value.
describe 'invoices index', {
subject-bang(:invoice, { Invoice.create(:amount(100)) });
it 'returns invoices', {
expect(Invoice.all.elems).to.be(1); # passes even though we never read :invoice
}
}subject-bang accepts the same three forms as subject:
subject-bang({ User.create });
subject-bang(:user, { User.create });
subject-bang('user', { User.create });It registers the let definition(s) and a before-each hook in the current group that forces :subject. Memoization is the same as subject: the block runs at most once per example.
When to use subject-bang
Use subject-bang when the creation of the subject is itself the test setup, typically for inserting database rows or otherwise mutating state that the example depends on existing before it runs. For pure values, plain subject is preferred because it stays cheap when the example doesn't actually read the value.
Restrictions
subject-bangmust be called inside adescribeorcontext. At the top-level (suite scope), only plainsubjectis supported.Inside an
itblock, usesubject()orexpect(:subject)to read the value. Eager forcing is meaningless once you're already in the example body.
is-expected
is-expected is a helper that builds an expect against the current :subject. It is the Behave equivalent of RSpec's is_expected.
describe 'a Widget', {
subject({ Widget.new(:bar(99)) });
it 'is a Widget', { is-expected.to.be(Widget) }
it 'is not a Gadget', { is-expected.not.to.be(Gadget) }
}is-expected reads the memoized subject value via $*LET-RUNTIME and returns an ExpectationBuilder, so the same .to / .not.to chain that you use with expect applies. Multiple calls within the same example share the subject's memoization: the subject block runs at most once per example.
Restrictions
is-expectedmust be called inside an example body.A
subject(orsubject-bang) must be defined in the enclosingdescribe/contextchain. Callingis-expectedwithout one raises a clear error.
One-liner it form
For terse, single-expectation examples you can omit the description:
describe 'a Widget', {
subject({ Widget.new(:bar(99)) });
it { is-expected.to.be(Widget) }
it { is-expected.not.to.be(Gadget) }
}When it is called with only a block, Behave generates the description from the first matcher invoked in the example. The above renders as:
ā® 'should be Widget'
ā® SUCCESS
ā® 'should not be Gadget'
ā® SUCCESSNegation (expect(...).not.to.be(...) or is-expected.not.to.be(...)) flips the verb to should not. If the block runs no matchers (for example, it { 1 + 1 }), Behave falls back to a placeholder of the form example at <basename>:<line>.
The block-only form composes with fit and xit:
fit { is-expected.to.be(42) } # focused one-liner
xit { is-expected.to.be(99) } # skipped one-linerDescription filtering
--example PATTERN matches against the description that was registered with the example, which for block-only it { ... } is the placeholder (e.g. example at foo-spec.raku:42), not the matcher-derived rendering. To filter on the rendered description, use a regular it 'should be X', { ... } form instead.
Comparison with let
subject is sugar around let(:subject, ...). The two are interchangeable when reading via :subject, but subject makes the intent ("this is what I'm testing") explicit and adds the dual-name alias when you give it a name. Use subject for the value under test and let for everything else (inputs, fixtures, helpers).