Custom Matchers¶
When the built-in matchers don't quite say what you mean, define your own.
A custom matcher is just a small bundle of callbacks: a match predicate plus
optional message/description hooks. It plugs into the same
Matcher machinery used by every built-in matcher.
define-matcher¶
1 2 3 4 5 6 7 8 9 10 | |
define-matcher returns a factory — call it with the matcher's expected
args and you get back a DefinedMatcher instance you can pass into
expect(...).to.be(...). The matcher is also registered globally so other
files can reach it through matcher(...).
Options¶
| Option | Required? | Block receives | Purpose |
|---|---|---|---|
match |
yes | $actual, *@args, *%kwargs |
Predicate. Return truthy to pass. |
failure-message |
no | $actual, *@args, *%kwargs |
Message rendered when the expectation fails. Default: undefined Str (falls back to Expected: / to be: rendering). |
failure-message-negated |
no | $actual, *@args, *%kwargs |
Message rendered when a .not expectation fails. Default: undefined Str. |
description |
no | *@args, *%kwargs |
Human-readable description (used for failure context and auto-description of it { ... }). Default: the matcher name. |
expected-value |
no | *@args, *%kwargs |
Value stored in Failure.expected. Default: the single arg, the kwargs Map, or the args List depending on what was passed. |
match is the only required block. Anything else can be omitted and you get a
sensible default. The factory you receive is just sugar over the
registry's build method; calling matcher(...) directly
works identically.
Argument shape¶
Every block receives the matcher's arguments after $actual. The shape mirrors
the factory call site:
1 2 3 4 5 | |
Named arguments are also supported and arrive as a slurpy hash:
1 2 3 4 | |
matcher(name, *args) lookup¶
When you only know the matcher's name at runtime, or want to look it up across
files without sharing the factory closure, use matcher:
1 2 | |
Looking up an unregistered name dies with a clear message.
Direct method dispatch via FALLBACK¶
ExpectationBuilder falls back to the custom-matcher registry for any unknown
method name, so once a matcher is registered you can call it as if it were a
built-in:
1 2 3 4 5 6 7 | |
Existing methods on ExpectationBuilder (be, eq, include, ...) take
precedence over the registry, and unknown names that aren't registered still
raise X::Method::NotFound — so a typo doesn't get silently swallowed.
How it plugs in¶
A DefinedMatcher does Matcher, so it goes through the exact same code path
as the built-in matchers:
expect(x).to.be($matcher)calls$matcher.matches($actual).- On a miss,
Failuresrecords the matcher'sfailure-message($actual)(or the negated variant under.not). - If you omit
failure-message, the failure renders through the structuredExpected:/to be:block plus the diff section, exactly likeBeMatcher.
This means custom matchers compose with everything else the runner already
does: .not, the
matcher architecture, aggregate-failures,
and one-liner auto-descriptions.
Validation errors¶
define-matcher rejects misuse up front:
| Mistake | Error |
|---|---|
Missing match block |
define-matcher '<name>': match block is required |
| Unknown option key | define-matcher '<name>': unknown option ':<key>' (allowed: match, ...) |
| Non-Callable value for any option | define-matcher '<name>': ':<key>' must be a Callable |
The registry¶
BDD::Behave::Matcher::Custom::registry() returns the singleton
CustomMatcherRegistry. It is mostly an implementation detail, but it exposes
a handful of useful methods:
| Method | Purpose |
|---|---|
register($name, %c) |
Internal. Used by define-matcher to register a configuration hash. |
exists($name) |
Does a matcher with this name exist? |
lookup($name) |
Return the raw config hash, or die if unknown. |
build($name, |c) |
Construct a DefinedMatcher (this is what matcher(...) calls). |
names() |
All registered names, sorted. |
clear() |
Wipe the registry. Useful in tests that re-register a name. |
Redefining a matcher with the same name replaces the previous registration; previously returned factory closures pick up the new definition the next time they are called.
Putting it together¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |