Skip to content

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:

1
2
3
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 :subject

When 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:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

1
2
3
4
5
6
7
8
9
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

1
2
3
4
5
6
7
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:

1
2
3
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-bang must be called inside a describe or context. At the top-level (suite scope), only plain subject is supported.
  • Inside an it block, use subject() or expect(: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.

1
2
3
4
5
6
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-expected must be called inside an example body.
  • A subject (or subject-bang) must be defined in the enclosing describe/context chain. Calling is-expected without one raises a clear error.

One-liner it form

For terse, single-expectation examples you can omit the description:

1
2
3
4
5
6
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:

1
2
3
4
⮑  'should be Widget'
  ⮑  SUCCESS
⮑  'should not be Gadget'
  ⮑  SUCCESS

Negation (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:

1
2
fit { is-expected.to.be(42) }   # focused one-liner
xit { is-expected.to.be(99) }   # skipped one-liner

Description 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).