composable-matchers

Composable Matchers

Combine matchers with .and and .or to express compound expectations without writing a one-off custom matcher. Every type that does the Matcher role gets these two methods automatically, so they work with built-in matchers, hand-rolled does Matcher classes, and define-matcher factories alike.

.and

AndMatcher requires every inner matcher to pass. It short-circuits at the first failure and reports which inner matcher rejected the value.

use BDD::Behave;
use BDD::Behave::Matcher::Core;
use BDD::Behave::Matcher::Numeric;

it 'is a small positive integer', {
  my $small-positive = BeMatcher.new(:expected(Int))
    .and(BeGreaterThanMatcher.new(:expected(0)))
    .and(BeLessThanMatcher.new(:expected(100)));

  expect(50).to.be($small-positive);       # passes
  expect(200).to.not.be($small-positive);  # passes
}

Pass multiple matchers to a single .and call to combine them in one step:

BeMatcher.new(:expected(Int)).and(
  BeGreaterThanMatcher.new(:expected(0)),
  BeLessThanMatcher.new(:expected(100)),
);

Chained .and calls flatten into a single AndMatcher, so the failure message and description stay readable regardless of how many you compose.

.or

OrMatcher passes when any inner matcher passes. It short-circuits at the first match and reports matched-index for tooling.

my $endpoint-ish = StartWithMatcher.new(:expected(['/api/']))
  .or(StartWithMatcher.new(:expected(['/v2/'])));

expect('/api/users').to.be($endpoint-ish);   # passes
expect('/v2/items').to.be($endpoint-ish);    # passes
expect('/admin').to.not.be($endpoint-ish);   # passes

As with .and, multiple-arg calls and chained calls both flatten.

Composing with custom matchers

define-matcher factories return ordinary matchers, so they compose the same way:

my &positive = define-matcher 'positive', match => -> $a { $a > 0 };
my &small    = define-matcher 'small',    match => -> $a { $a < 100 };

expect(50).to.be(positive().and(small()));     # passes
expect(200).to.not.be(positive().and(small())); # passes

Mixing .and and .or

Composition is left-associative: each call returns a new composite, so chains build up naturally.

# (A and B) or C
$a.and($b).or($c);

# A and (B or C): group with an explicit inner composite
$a.and($b.or($c));

Reach for an explicit nested matcher when precedence matters.

Negation

.not flips the composite result the same way it flips any other matcher:

CompositeUnder .not passes when…
AndMatcherAt least one inner matcher fails
OrMatcherEvery inner matcher fails

Failure messages for the negated forms tell you why the composite ended up matching when it wasn't supposed to (which AndMatcher couldn't escape, or which OrMatcher branch matched).

Failure messages

AndMatcher failure messages identify the first inner matcher that rejected the value:

expected 200 to be greater than 0 and be less than 100, but be less than 100 failed: expected 200 to be less than 100

OrMatcher failure messages list every inner matcher and note that none matched:

expected 7 to be 5 or be 10, but none matched

Negated AndMatcher and OrMatcher failure messages mirror the structure above with not to ….

Inspecting composites

Both composites expose useful state for diagnostics and custom formatters:

MethodReturns
.matchersThe flattened inner matcher list.
AndMatcher.failing-indexIndex of the first inner matcher that failed (or Int).
AndMatcher.failing-matcherThe first inner matcher that failed (or Nil).
OrMatcher.matched-indexIndex of the first inner matcher that matched.
OrMatcher.matched-matcherThe first inner matcher that matched.
.expected-valueA List of every inner matcher's expected-value.
.descriptionInner descriptions joined with and / or.

Argument validation

.and and .or accept only objects that do the Matcher role. Passing anything else dies immediately with a clear message: composition catches typos at the call site rather than papering over them with surprising smartmatch semantics.

BDD::Behave v0.9.4

Behavior driven development framework

Authors

  • Greg Donald

License

Artistic-2.0

Dependencies

Provides

  • BDD::Behave
  • BDD::Behave::Benchmark
  • BDD::Behave::Benchmark::Baseline
  • BDD::Behave::Benchmark::Format
  • BDD::Behave::Bisect
  • BDD::Behave::Colors
  • BDD::Behave::Configuration
  • BDD::Behave::Coverage
  • BDD::Behave::DSL
  • BDD::Behave::Diff
  • BDD::Behave::DocExtractor
  • BDD::Behave::DryRun
  • BDD::Behave::Expectation
  • BDD::Behave::Failure
  • BDD::Behave::FailureStore
  • BDD::Behave::Failures
  • BDD::Behave::Files
  • BDD::Behave::Formatter
  • BDD::Behave::Formatter::Documentation
  • BDD::Behave::Formatter::HTML
  • BDD::Behave::Formatter::JSON
  • BDD::Behave::Formatter::JUnit
  • BDD::Behave::Formatter::JsonEvents
  • BDD::Behave::Formatter::Progress
  • BDD::Behave::Formatter::Registry
  • BDD::Behave::Formatter::TAP
  • BDD::Behave::Formatter::Tree
  • BDD::Behave::LetRuntime
  • BDD::Behave::Matcher
  • BDD::Behave::Matcher::Async
  • BDD::Behave::Matcher::Boolean
  • BDD::Behave::Matcher::Change
  • BDD::Behave::Matcher::Collection
  • BDD::Behave::Matcher::Core
  • BDD::Behave::Matcher::Custom
  • BDD::Behave::Matcher::Exception
  • BDD::Behave::Matcher::Numeric
  • BDD::Behave::Matcher::String
  • BDD::Behave::Matcher::Type
  • BDD::Behave::Mock::Allow
  • BDD::Behave::Mock::ArgMatcher
  • BDD::Behave::Mock::Double
  • BDD::Behave::Mock::HaveReceived
  • BDD::Behave::Mock::Spy
  • BDD::Behave::Mock::Stub
  • BDD::Behave::Parallel
  • BDD::Behave::Parallel::Distribution
  • BDD::Behave::Parallel::EventStream
  • BDD::Behave::Parallel::Manifest
  • BDD::Behave::Parallel::Queue
  • BDD::Behave::Parallel::WorkerPool
  • BDD::Behave::Runner
  • BDD::Behave::SharedContexts
  • BDD::Behave::SharedExamples
  • BDD::Behave::Slang
  • BDD::Behave::SpecLoader
  • BDD::Behave::SpecRegistry
  • BDD::Behave::SpecTree
  • BDD::Behave::SpecTree::Core
  • BDD::Behave::SpecTree::Example
  • BDD::Behave::SpecTree::ExampleGroup
  • BDD::Behave::SpecTree::Suite
  • BDD::Behave::Time
  • BDD::Behave::TypeName
  • BDD::Behave::Version
  • BDD::Behave::Watch
  • BDD::Behave::Watch::Session
  • BDD::Behave::Watch::SmartSelector
  • BDD::Behave::Watch::UI
  • BDD::Behave::Watch::Watcher
  • BDD::Behave::Worker

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.