Matcher Architecture¶
expect(...).to.be(...) is built on top of a small Matcher role. The expected
value passed to .be(...) is either:
- a plain value — wrapped in the built-in
BeMatcher(smartmatch), or - a
Matcher-doing object — used directly.
This is the seam every built-in matcher and every user-defined matcher plugs
into. If you want to add your own matcher without writing a class by hand, see
Custom Matchers — define-matcher produces a matcher
that conforms to this same role.
The Matcher role¶
1 2 3 4 5 6 7 8 9 | |
| Method | Required? | Purpose |
|---|---|---|
matches($actual) |
yes | Return True / False for whether $actual matches. |
failure-message($actual) |
no | Message rendered when the expectation fails (positive form). Default: undefined Str (falls back to Expected: / to be: rendering). |
failure-message-negated($actual) |
no | Message rendered when a .not expectation fails. Default: undefined Str. |
expected-value |
no | The value stored in Failure.expected for tooling. |
description |
no | Human-readable description, useful for error reporting and reflection. |
Where matcher classes live¶
Almost all users only ever write use BDD::Behave; — that pulls in expect, every built-in matcher, and the full DSL through the lazy-loading wrapper. Nothing here changes that.
You only need to know about specific submodules when you want to instantiate matcher classes directly — typically to compose them inside a custom matcher, or to unit-test a matcher with Test. Each family lives in its own submodule so unit tests can import just one and skip the rest:
| Submodule | Matcher classes |
|---|---|
BDD::Behave::Matcher |
The Matcher role itself. |
BDD::Behave::Matcher::Core |
BeMatcher, EqMatcher |
BDD::Behave::Matcher::Collection |
IncludeMatcher, ContainExactlyMatcher, StartWithMatcher, EndWithMatcher, AllMatcher |
BDD::Behave::Matcher::Type |
BeAMatcher, BeAnInstanceOfMatcher, RespondToMatcher, HaveAttributesMatcher |
BDD::Behave::Matcher::Numeric |
BeGreaterThanMatcher, BeGreaterThanOrEqualMatcher, BeLessThanMatcher, BeLessThanOrEqualMatcher, BeBetweenMatcher, BeWithinMatcher |
BDD::Behave::Matcher::Boolean |
BeTruthyMatcher, BeFalsyMatcher, BeNilMatcher |
BDD::Behave::Matcher::String |
MatchMatcher |
BDD::Behave::Matcher::Exception |
RaiseErrorMatcher |
BDD::Behave::Matcher::Change |
ChangeMatcher |
BDD::Behave::Matcher::Async |
BeKeptMatcher, BeBrokenMatcher, CompleteWithinMatcher, EmitMatcher, EmitAtLeastMatcher, CompleteMatcher, EventuallyMatcher |
Each submodule already uses BDD::Behave::Matcher internally, but the role is not re-exported. If your code needs both, import both:
1 2 | |
BeMatcher (built-in)¶
BeMatcher wraps Raku's smartmatch operator (~~). When you write:
1 2 3 4 | |
…the runner constructs BeMatcher.new(:expected(...)) under the hood. Because
BeMatcher deliberately leaves failure-message undefined, failure rendering
keeps the structured Expected: / to be: block plus the colorized Diff:
section described in Diff Output.
Because the underlying operator is ~~, all four Raku junction kinds (any,
all, one, none) compose with BeMatcher directly — see
Junctions.
EqMatcher (built-in)¶
eq checks order-dependent structural equality using Raku's eqv operator.
It's invoked via expect(...).to.eq(...):
1 2 3 4 5 | |
eqv is type-strict, so an Array is not equivalent to a List with the same
elements:
1 | |
EqMatcher deliberately leaves failure-message undefined, so failures fall
through to the structured Expected: / to be: block plus the colorized
Diff: section described in Diff Output.
Negation works the usual way:
1 | |
ContainExactlyMatcher (built-in)¶
contain-exactly checks order-independent multiset equality on arrays / lists.
Each item in actual must correspond to one item in the expected list (matched
by eqv), with counts and totals matching:
1 2 3 4 5 | |
Items are passed as individual positional arguments. The slurp is
non-flattening, so passing a single array (contain-exactly([1, 2])) looks for
that array as one element. To spread an existing array, use |@arr:
1 2 | |
The empty form passes for an empty array:
1 | |
Failure messages render as expected <actual> to contain exactly <items> (or
not to contain exactly under .not).
match-array is the array-form alias — it takes a single array argument and
delegates to the same matcher:
1 2 | |
match-array requires its argument to be an array / list; passing a scalar
dies with match-array requires an array argument.
IncludeMatcher (built-in)¶
include checks membership across arrays, hashes, sets, bags, ranges, and
strings. It's invoked via expect(...).to.include(...):
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Multiple items are combined with AND semantics: every item must be present
for the matcher to pass. The slurp is non-flattening, so passing a single
array argument (include([1, 2])) looks for that array as one element rather
than spreading it. To spread an existing array, use |@arr.
Negation works the usual way:
1 | |
Failure messages render as expected <actual> to include <items> (or not
to include under .not).
String-specific behavior¶
When the actual is a Str, include does substring matching via
.contains. A few details worth knowing:
1 2 3 4 5 6 7 | |
Matching is case sensitive — expect('Hello').to.include('hello') fails.
For case-insensitive matching, use MatchMatcher
with a regex like rx:i/hello/.
Allomorphs (IntStr, RatStr, <42>) are accepted as actuals and routed
through the Str branch since they smartmatch as Str.
StartWithMatcher (built-in)¶
start-with checks that a sequence (Array, List) begins with the supplied
items, or that a string begins with each supplied prefix:
1 2 3 4 5 6 7 8 9 | |
For Positional / Iterable actuals, the args form an in-order prefix matched
via eqv. For Str actuals, each arg must independently be a prefix of the
string (AND semantics).
The slurp is non-flattening, so passing a single array (start-with([1, 2]))
looks for that array as one prefix element. To spread an existing array, use
|@arr.
start-with rejects undefined or non-iterable, non-string actuals. Empty arg
lists die with start-with requires at least one item.
Failure messages render as expected <actual> to start with <items> (or not
to start with under .not).
String-specific behavior¶
When the actual is a Str, each prefix is checked via .starts-with after
.Str coercion. Multi-prefix calls AND together — every supplied prefix must
independently be a prefix of the string:
1 2 3 4 5 6 7 | |
Matching is case sensitive and respects leading whitespace —
expect('hello').to.start-with(' hello') fails. Allomorphs (IntStr,
RatStr) are accepted as actuals and routed through the Str branch.
EndWithMatcher (built-in)¶
end-with mirrors start-with for the trailing end of a sequence or string:
1 2 3 4 5 6 7 8 9 | |
Same slurp / type / empty-arg conventions as start-with. Failure messages
render as expected <actual> to end with <items> (or not to end with under
.not).
String-specific behavior¶
When the actual is a Str, each suffix is checked via .ends-with after
.Str coercion. Multi-suffix calls AND together — every supplied suffix must
independently be a suffix of the string:
1 2 3 4 5 6 7 | |
Matching is case sensitive and respects trailing whitespace —
expect('hello').to.end-with('hello ') fails. Allomorphs (IntStr, RatStr)
are accepted as actuals and routed through the Str branch.
AllMatcher (built-in)¶
all checks that every element of a collection matches an inner matcher.
The inner argument is either a plain value (wrapped in BeMatcher, smartmatch
semantics) or any object that does Matcher:
1 2 3 4 5 6 7 8 9 | |
An empty collection passes vacuously:
1 2 | |
Undefined or non-iterable actuals fail with a shape failure message
(expected ... to be a collection ...). For sequence actuals, the matcher
iterates $actual.list, so Hash actuals are iterated as Pairs.
Failure messages render as
expected <actual> to all <inner-description> (element at index N: <item> did not match),
pointing at the first failing element. Negation renders as
expected <actual> not to all <inner-description>.
Composing across collections of collections¶
all is most useful when the inner matcher is itself a structural matcher:
1 2 3 4 5 6 | |
Junctions¶
Pass junctions through .all(...) directly — the method binds its argument
raw so junctions are not autothreaded out:
1 | |
BeAMatcher (built-in)¶
be-a checks whether the actual value is "of" a given type, including
subclasses, composed roles, and subset types. Internally it smartmatches
the actual value against the type ($actual ~~ $type):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
be-an is provided as an alias for English grammar — it behaves identically:
1 | |
Failure messages render as expected <actual> to be a <type> (or not to be
a <type> under .not). The type name comes from $type.^name.
BeAnInstanceOfMatcher (built-in)¶
be-an-instance-of is a strict type check: the actual value's runtime
type must be exactly the given type. Subclasses, composed roles, and subsets
do not match. Internally it checks $actual.defined && $actual.WHAT === $type:
1 2 3 4 | |
Type objects (the uninstantiated type itself) do not match:
1 | |
Because composed roles and subsets are not the runtime type of any concrete
object, be-an-instance-of(SomeRole) and be-an-instance-of(SomeSubset)
always fail. Use be-a for those cases.
Failure messages render as expected <actual> to be an instance of <type>
(or not to be an instance of <type> under .not).
RespondToMatcher (built-in)¶
respond-to checks whether the actual value has one or more methods.
Internally it uses the meta-object protocol's ^can introspection
($actual.^can($name)), so it works on both instances and type objects,
and recognises methods supplied by composed roles and parent classes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Multiple method names are AND-combined — every name must be present for the expectation to pass. When the expectation fails, the failure message lists the missing methods:
1 | |
Negation works the usual way:
1 | |
Failure messages render as expected <actual> to respond to <names>
(or not to respond to <names> under .not).
HaveAttributesMatcher (built-in)¶
have-attributes checks several attributes of an object in one call.
Each named pair maps an attribute name to an expected value (or to
another Matcher). For each pair, the matcher calls the accessor on
the actual value and compares — values are compared with eqv, and
when the expected side is itself a Matcher its matches method is
delegated to.
1 2 3 4 5 6 7 8 9 10 | |
Multiple attributes are AND-combined — every pair must match. When the expectation fails, the failure message separates missing accessors (the object does not respond to the name at all) from mismatched values (accessor exists, but the value disagrees):
1 | |
The matcher composes naturally with other matchers — pass a Matcher
instance as the expected value for any attribute:
1 2 3 4 5 6 7 | |
Negation works the usual way:
1 | |
Failure messages render as expected <actual> to have attributes
<pairs> (or not to have attributes <pairs> under .not).
Comparison matchers (built-in)¶
Four matchers cover numeric ordering:
| Matcher | Operator | Aliases |
|---|---|---|
be-greater-than |
> |
be-gt |
be-greater-than-or-equal-to |
>= |
be-gte |
be-less-than |
< |
be-lt |
be-less-than-or-equal-to |
<= |
be-lte |
1 2 3 4 5 6 7 8 9 | |
All four accept any Real value, so Int, Rat, and Num mix freely
and negatives / zero behave as expected:
1 2 3 4 5 | |
Each matcher fails (rather than dying) when $actual is undefined or
is not a Real, so a stray Int-typed Nil or a Str produces a
recorded failure instead of a runtime error:
1 2 | |
Negation works the usual way:
1 2 | |
Failure messages render as expected <actual> to be greater than
<expected> (and the obvious variants for the other three), or not to
be … under .not.
BeBetweenMatcher (built-in)¶
be-between checks whether a numeric actual falls within a [min, max]
range. The matcher is inclusive by default; chain .exclusive (or the
explicit .inclusive) to flip the mode:
1 2 3 4 5 6 7 8 9 | |
All four call sites use the same chainable expectation. Re-chaining a
modifier replaces any previously recorded failure, so
expect(10).to.be-between(1, 10).exclusive.inclusive ends with no
failure: the .exclusive step pushes one, then .inclusive clears it
when the re-evaluation passes.
be-between accepts any Real actual, so Int, Rat, and Num mix
freely and negative / zero bounds behave the obvious way:
1 2 3 4 | |
The matcher fails (rather than dying) when $actual is undefined or is
not a Real, so a stray Int-typed Nil or a Str produces a
recorded failure instead of a runtime error:
1 2 | |
Negation works the usual way, and composes with the inclusive / exclusive modifiers:
1 2 | |
Failure messages name both bounds and the active mode:
1 2 | |
Failure.expected is populated as [min, max] so programmatic
consumers and alternate formatters can introspect the bounds.
BeWithinMatcher (built-in)¶
be-within performs a tolerance check on a numeric actual against an
expected target, parameterized by a delta. The call shape uses an
.of(...) continuation so the delta and expected target read naturally:
1 2 3 4 | |
The boundary is inclusive — abs(actual - expected) <= delta — so a
delta of 0 means actual must equal expected exactly:
1 2 | |
be-within accepts any Real actual and target, so Int, Rat, and
Num mix freely. Negative values and zero behave the obvious way:
1 2 | |
The matcher fails (rather than dying) when either $actual or the
target passed to .of(...) is undefined or non-Real, so a stray
Int-typed Nil or a Str produces a recorded failure instead of a
runtime error:
1 2 3 | |
Negation works the usual way:
1 2 | |
Failure messages render the delta and expected target:
1 2 | |
Failure.expected is populated with the expected target (not the
delta), and Failure.given holds the actual value, so programmatic
consumers and alternate formatters can introspect both.
BeTruthyMatcher (built-in)¶
be-truthy checks Raku's boolean coercion of the actual value (?$actual).
Anything that coerces to True passes; anything that coerces to False
fails:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Raku's coercion may surprise users coming from Perl: non-empty strings
are always truthy ('0'.Bool is True), but an empty Array / Hash
is False:
1 2 3 | |
be-truthy takes no arguments. Negation works the usual way:
1 | |
Failure messages render as expected <actual> to be truthy (or not to
be truthy under .not).
BeFalsyMatcher (built-in)¶
be-falsy is the inverse of be-truthy — it passes when !$actual is
True:
1 2 3 4 5 6 7 8 9 10 11 12 | |
be-falsy takes no arguments. Failure messages render as
expected <actual> to be falsy (or not to be falsy under .not).
BeNilMatcher (built-in)¶
be-nil passes when the actual value is undefined (!$actual.defined).
That covers Raku's three flavors of "not a value":
NilitselfAny(the default for an unassigned scalar)- any type object, including built-ins like
Int/Strand user-defined classes
1 2 3 4 5 6 7 8 | |
Defined values — even "empty" or falsy ones — fail be-nil:
1 2 3 4 5 | |
This is the type-object-vs-instance distinction that Raku makes
explicit: Int (the type) is undefined, but 0 (an instance of
Int) is defined. Use be-nil when you care about "is this a real
value", and be-falsy when you care about boolean coercion (which
treats empty collections as false too).
Note that assigning Nil to a plain my $x reverts $x to Any —
both still pass be-nil, so the distinction rarely matters in
practice.
be-nil takes no arguments. Failure messages render as
expected <actual.raku> to be nil (or not to be nil under .not).
MatchMatcher (built-in)¶
match passes when a Str actual value smartmatches against a Regex
expected value ($actual ~~ /pattern/). It accepts the full Raku regex
syntax, including character classes, alternation, anchors, modifiers,
and rx//-quoted forms.
1 2 3 4 | |
Negation uses the same .not chain:
1 2 | |
Undefined and non-Str actuals fail rather than throw — expect(Any),
expect(Str), expect(42), and expect([1, 2, 3]) all record a
failure when matched against any regex, so a stray nil or wrongly typed
value produces a normal expectation failure instead of a runtime
exception.
Failure messages render the actual value and the regex via .raku:
1 2 | |
Failure.given carries the original string and Failure.expected
carries the Regex itself, so programmatic consumers and alternate
formatters can inspect either side.
match is regex-only; for substring checks use include (see
Matchers › IncludeMatcher).
RaiseErrorMatcher (built-in)¶
raise-error passes when the actual value is a Callable and invoking it
raises an exception. Because the matcher operates on a Callable, the
block must be passed unevaluated — wrap the code under test in { ... }:
1 2 3 | |
Filtering by exception type¶
Pass an exception type as the first argument to require that the raised
exception is that type, a subclass of it, or a class that does it as a
role (matching uses smartmatch, i.e. $exception ~~ $type):
1 2 3 4 5 6 | |
When the block raises a different type, the failure surfaces what actually happened:
1 | |
When the block does not raise at all under a typed expectation:
1 | |
Failure.expected carries the expected type, so programmatic consumers
and alternate formatters can read it directly.
Filtering by message pattern¶
Pass a Regex (with or without a leading type) to require that the
raised exception's .message matches the regex:
1 2 3 | |
When the type matches but the message does not (or vice versa), the failure says what was raised so the mismatch is diagnosable inline:
1 2 | |
Filtering by message via with-message¶
raise-error also supports a chained .with-message(...) form that
filters by either an exact Str or a Regex. The chain is equivalent
to passing the regex inline but reads more naturally when the message
constraint is a string:
1 2 3 | |
Str arguments compare with eq (the full message must match
exactly); Regex arguments compare with ~~. The chain may be
re-applied — only the most recent call's expectation is in effect, and
any failure recorded by an earlier link in the chain is replaced rather
than appended:
1 2 3 4 5 | |
When the chain ultimately fails, the failure message reflects the final constraint:
1 2 | |
.with-message also composes with .not:
1 2 | |
Negation¶
.not.raise-error(...) passes whenever the block fails to match the
full filter — i.e. nothing was raised, or a different type was raised,
or the message did not match. It only fails when the block raised
exactly the forbidden exception:
1 2 3 | |
When .not.raise-error fails, the negated failure includes the raised
exception's type and message so you can diagnose without re-running:
1 2 | |
Non-Callable actuals¶
Non-Callable actuals fail rather than throw — expect(42).to.raise-error
records a normal expectation failure rather than blowing up. The failure
message names the unwrapped value so the mistake is obvious:
1 | |
Failure metadata¶
Failure.given carries the original Callable. Failure.expected
carries the expected exception type (when one was supplied), or the
expected message (regex or Str, whichever was supplied via the inline
form or with-message); for the bare no-argument form it remains
unset. The matcher itself also exposes raised-exception (the captured
throw) and miss-reason (one of 'none', 'type', 'message',
'non-callable', or Str on a pass) for tooling that needs structural
access.
ChangeMatcher (built-in)¶
change passes when invoking the actual block changes the value
returned by an observable block. Both the action and the observable
are passed unevaluated as { ... } blocks — the matcher invokes the
observable, runs the action, then invokes the observable again and
compares the two values via eqv:
1 2 3 4 5 6 7 8 | |
The observable can be any expression — a variable, a method call, a
derived value — as long as the block returns something comparable. The
matcher uses eqv for the before / after comparison, so deep
structural equality is respected: mutating an array element to the
same value (@items[0] = 1 when it was already 1) does not
register as a change.
Capturing snapshots¶
Because eqv compares the values returned by the observable each
time, an observable like { @items } returns the same container on
both calls. To detect element-level mutation, snapshot the value:
1 2 3 | |
Negation¶
.not.change passes when the action does not change the
observable, and fails when it does:
1 2 3 4 5 | |
Non-Callable actuals¶
Non-Callable actuals fail rather than throw — expect(42).to.change({ $x })
records a normal expectation failure rather than blowing up. The
failure message names the unwrapped value:
1 | |
Filtering with .from and .to¶
change returns a chainable expectation that can be narrowed with
.from(value) and .to(value) to assert the precise transition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
.from and .to use eqv for the comparison, so deep structural
equality is respected (e.g. .from([]) matches an empty-array
snapshot). The action block is run exactly once per .change(...)
call — chaining .from(...).to(...) after a .change(...) does
not re-run it.
When a chain step fails and a later step passes, the prior failure
is replaced (rather than duplicated) so the recorded Failure list
always reflects the final state.
Negation with .from / .to¶
The chain participates in the existing .not flow with conjunction
semantics: .not.change(...).from(x).to(y) passes when the joint
constraint (change AND from match AND to match) does not hold.
This means the block may still have changed the observable — just
not in that particular direction. The negated failure message
includes the clause for clarity:
1 2 3 4 5 6 7 | |
Filtering with .by / .by-at-least / .by-at-most¶
For numeric observables, .by(delta) asserts the precise signed
change, while .by-at-least(delta) and .by-at-most(delta) bound
the change from below and above:
1 2 3 4 5 6 7 8 9 10 11 | |
All three accept any Real (Int, Rat, Num); the matcher
subtracts after - before and compares against the expected delta
with the usual numeric operators. If the observable returns a
non-numeric value (e.g. a Str), the matcher records a
by-non-numeric failure rather than coercing.
The by-modifiers compose with .from(...) and .to(...). From
and to mismatches still take priority — .from(0).by(5) reports
a from-mismatch when the value did not start at 0, even if the
delta would otherwise be wrong. RSpec parity: when the value did
not change at all, the no-change miss-reason fires first, so
.by(0) is effectively unreachable.
When a chain step fails and a later step passes, the prior
failure is replaced (rather than duplicated) so the recorded
Failure list always reflects the final state.
Failure messages¶
1 2 3 4 5 6 7 8 9 10 11 | |
Failure metadata¶
Failure.given carries the original action Callable.
Failure.expected reflects the most-specific constraint
supplied: [from, to] when both endpoints are set, the from / to
value alone when only one is set, the by / by-at-least /
by-at-most value when only a delta constraint is set, or Nil
for the bare form. The matcher itself exposes before-value,
after-value, action-ran, callable-given, and miss-reason
(one of 'non-callable', 'from', 'to', 'no-change',
'by', 'by-at-least', 'by-at-most', 'by-non-numeric', or
Str on a pass) for tooling that needs structural access to the
captured states. delta returns after - before when both
values are Real, and Nil otherwise.
BeKeptMatcher (built-in)¶
be-kept waits for a Promise to settle and passes when it ends in the
Kept state. It blocks up to a configurable timeout (default 5 seconds)
using Promise.anyof(...) so it never hangs forever:
1 2 3 4 | |
If the promise broke, the failure message includes the cause's class and message so you can see what went wrong without rerunning:
1 | |
If the timeout elapsed with the promise still pending, the message quotes the timeout:
1 | |
The matcher exposes value (set when the promise was kept),
cause (set when the promise was broken), timed-out, and
promise-given for tooling.
be-kept accepts a positional Real timeout in seconds; pass 0.5
for 500 ms. Non-Promise actuals fail with a Promise-shape message
(expected a Promise for be-kept, but got 42) rather than dying.
BeBrokenMatcher (built-in)¶
be-broken is the mirror of be-kept: it waits up to the timeout
(default 5 seconds) and passes when the promise ends in the
Broken state. The captured cause is exposed on the matcher's
cause accessor:
1 2 3 4 | |
When the promise was kept instead, the failure message includes the kept value:
1 | |
When the promise was still pending after the timeout:
1 | |
The negated form surfaces the broken cause so you can see what broke when you expected it not to:
1 | |
The matcher exposes value (set when the promise was kept), cause
(set when broken), was-kept, timed-out, and promise-given.
CompleteWithinMatcher (built-in)¶
complete-within passes when the promise settles — either kept or
broken — within the given duration. The duration is a Real
positional argument in seconds:
1 2 3 4 5 | |
The failure message when the promise is still pending:
1 | |
The negated form indicates the final status:
1 2 | |
Failure.expected carries the duration so failure metadata is
queryable from tooling. The matcher exposes final-status (Kept,
Broken, or Nil when timed out), value, cause, timed-out,
and promise-given.
Unlike be-kept / be-broken, the duration is required — there's
no useful default for "how long is too long" without it.
EmitMatcher (built-in)¶
emit taps a Supply or iterates a Channel and passes when the
source emits exactly the expected sequence of values within a
configurable collection window. Comparison is element-by-element via
eqv:
1 2 3 4 5 6 7 8 | |
The collection window defaults to 1 second. Pass :within(seconds) to
change it — useful for slow streams or to fail fast on hung supplies:
1 | |
emit uses react/whenever under the hood and stops collecting as
soon as the expected number of values arrive (the window is only the
maximum wait). This makes the matcher fast for known-length sequences.
Failure messages render both the expected and the actually-emitted sequences:
1 | |
Non-stream actuals (anything that's neither Supply nor Channel)
record a shape-failure rather than dying:
1 | |
Failure.expected carries the expected values list for tooling.
EmitAtLeastMatcher (built-in)¶
emit-at-least passes when a Supply or Channel emits at least the
given number of values within the collection window. The matcher stops
collecting as soon as the threshold is reached:
1 2 | |
The :within window defaults to 1 second.
Failure messages report the count that was actually emitted:
1 | |
Failure.expected carries the minimum count.
CompleteMatcher (built-in)¶
complete waits for a Supply to send done (or for a Channel to
be closed) within the collection window:
1 2 3 4 5 6 7 8 | |
The :within window defaults to 1 second.
Failure messages report whether the stream was still active or had errored:
1 2 | |
Failure.expected carries the window so failure metadata is queryable
from tooling.
Unlike Promise's complete-within, the complete matcher does not
treat a stream-level error (QUIT) as completion — only the done
signal counts.
EventuallyMatcher (built-in)¶
eventually re-runs a Callable block on a polling loop until the
inner matcher passes or the timeout elapses. Useful for asserting on
eventually-consistent state without hand-rolled polling:
1 2 3 | |
The chained matcher methods (.be, .eq, .match, .include,
.be-truthy, .be-a, .be-greater-than, etc.) build the appropriate
inner matcher and feed each polled value through it. For matchers
outside the built-in chained set, pass a Matcher instance through
.matches-with:
1 | |
The eventually(...) call takes two named arguments:
| Name | Default | Meaning |
|---|---|---|
:timeout |
2.0 | Max seconds to wait for a match |
:interval |
0.05 | Seconds to sleep between iterations |
Polling exits as soon as the inner matcher passes, so a passing assertion completes quickly. A miss runs out the full timeout before recording a failure.
Failure messages report the iteration count and the elapsed wall-clock time:
1 | |
If the block throws on every iteration, the failure message names the last exception:
1 | |
Exceptions raised inside the block are caught per-iteration and treated as misses — the polling loop continues so the block has a chance to recover (e.g., a service warming up).
Negation¶
Under .not, eventually passes only when the inner matcher never
matched for the full timeout window:
1 | |
Because polling exits at the first match, a negated assertion that matches early fails quickly rather than waiting out the timeout.
Failure metadata¶
Failure.givenis theCallableblock (preserved as-is)Failure.expectedis the inner matcher'sexpected-valueFailure.messageincludes iteration count and elapsed time
Non-Callable actuals¶
expect(42).to.eventually.be(42) records a Promise-shape failure
because the actual is not a Callable. The block form is required.
Writing a custom matcher¶
Define a class that does Matcher and pass an instance to .be(...):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
When the matcher reports a failure:
- The matcher's
failure-message($actual)(orfailure-message-negated($actual)for.not) becomesFailure.messageand is what the failure summary prints. Failure.givenholds the actual value,Failure.expectedholdsexpected-value. Both are still useful for programmatic consumers and for alternate formatters.
Negation¶
.not flips the boolean result of matches before the framework decides
whether to record a failure. Matchers do not need to special-case negation;
they only need to return a sensible message from failure-message-negated for
when .not fails (i.e., the matcher matched but should not have).
Built-in matchers and user-supplied matchers go through the same role, so a
custom matcher you write integrates with expect exactly the way the
built-ins do.
For the DSL-form alternative — defining matchers as a bundle of callbacks without writing a class — see Custom Matchers.
Composing matchers¶
Every type that does Matcher automatically gets .and and .or methods
that build AndMatcher / OrMatcher composites. They short-circuit, flatten
across chained calls, and report which inner matcher decided the outcome. See
Composable Matchers for details.