One of RSpec 3’s big new features is shipping 3.0.0.beta2: composable matchers. This feature supports more powerful, less brittle expectations, and opens up new possibilities.
In RSpec 2.x, I’ve written code like this on many occassions:
In RSpec 3, composable matchers allow you to pass matchers as arguments (or nested within data structures passed as arguments) to other matchers allowing you to simplify specs like these:
We’ve made sure the failure messages read well for cases like these, opting to use the
description of the provided matcher rather than the
inspect output. For example, if we “break” the implementation tested by this spec by commenting out the
queue << ... line, it fails with:
As you may have noticed, the example above uses
a_hash_including in place of
include. RSpec 3 provides similar aliases for all of the built-in matchers that read better grammatically and provide a better failure message.
For example, compare this expectation and failure message:
a_string_starting_with is more verbose than
start_with, it produces a failure message that actually reads well, so you don’t trip over odd grammatical constructs. We’ve provided one or more similar aliases for all of RSpec’s built-in matchers. We’ve tried to use consistent phrasing (generally “a [type of object] [verb]ing”) so they are easy to guess. You’ll see many examples below, and the RSpec 3 docs will have a full list.
There’s also a public API that makes it trivial to define your own aliases (either for RSpec’s built in matchers or for a custom matcher). Here’s the bit of code in rspec-expectations that provides the
a_string_starting_with alias of
Compound Matcher Expressions
Eloy Espinaco contributed a new feature that provides another way of combining matchers: compound
or matcher expressions. For example, rather than writing this:
…you can combine these into one expectation:
You can do the same with
or. While less common, this is useful for expressing one of a valid list of values (e.g. when the exact value is indeterminite):
I think this could particularly come in handy for expressing invariants using Jim Weirich’s rspec-given.
Compound matcher expressions can also be passed as an argument to another matcher:
Note: in this example,
ending_with is another alias for the
Which matchers support matcher arguments?
In RSpec 3, we’ve updated many of the matchers to support receiving matchers as arguments, but not all of them do. In general, we updated all of the ones where we felt like it made sense. The ones that do not support matchers are those that have precise matching semantics that do not allow for a matcher argument. For example the
eq matcher is documented as passing if and only if
actual == expected. It doesn’t make sense for
eq to support receiving a matcher argument1.
I’ve compiled a list below of all the built-in matchers that support receiving matchers as arguments.
by method of the
change matcher can receive a matcher:
You can also pass matchers to
contain_exactly is a new alias of
match_array. The semantics are a bit more clear than
match_array (now that
match can match arrays, too, but
match requires the ordering to match whereas
match_array doesn’t). It also allows you to pass the array elements as individual arguments rather than being forced to pass a single array argument like
include allows you to match against the elements of a collection, the keys of a hash, or against a subset of the key/value pairs in a hash:
In addition to matching a string against a regex or another string,
match now works against arbitrary array/hash data structures, nested as deeply as you like. Matchers can be used at any level of that nesting:
raise_error can accept a matcher for matching against the exception class or a matcher to match against the message, or both.
start_with and end_with
These are pretty self-explanatory:
You can pass a matcher to
throw_symbol to match against the accompanying argument:
yield_with_args and yield_successive_args
Matchers can be used to specify the yielded arguments for these matchers:
This is one of the new features of RSpec 3 I’m most excited about and I hope you can see why. This should help make it easier to avoid writing brittle specs by enabling you to specify exactly what you expect (and nothing more).
You can, of course pass a matcher to↩
eq, but it’ll treat it just like any other object: it’ll compare it to
==, and, if that returns true (i.e. if it’s the same object), the expectation will pass.