In Putting the Pieces Together, we saw RSpec’s not_to and to_not methods, which specify that a given condition should not hold:
| expect(correct_grammar).to_not split_infinitives |
If you find yourself doing this over and over again, you can define a negated matcher that you’d use like so:
| expect(correct_grammar).to avoid_splitting_infinitives |
It’s easy to create your own negated matcher. All you have to do is call define_negated_matcher:
| RSpec::Matchers.define_negated_matcher :avoid_splitting_infinitives, |
| :split_infinitives |
As with alias_matcher, you pass the name of the new matcher, followed by the old one. The avoid_splitting_infinitives matcher will now behave as the negation of split_infinitives.
Negative matchers come in handy for more complex cases, such as when you’re combining matchers. For example, the following expectation is ambiguous, and RSpec warns us of this problem:
| >> expect(adverb).not_to start_with('a').and end_with('z') |
| NotImplementedError: `expect(...).not_to matcher.and matcher` is not ↩ |
| supported, since it creates a bit of an ambiguity. Instead, define negated ↩ |
| versions of whatever matchers you wish to negate with ↩ |
| `RSpec::Matchers.define_negated_matcher` and use `expect(...).to ↩ |
| matcher.and matcher`. |
| backtrace truncated |
The ambiguity is subtle: should the adverb “absolutely” match (because it satisfies the “does not end with z” condition)? Or should it fail to match (because it does not satisfy both conditions)?
RSpec’s error message points out the ambiguity and suggests negated matchers as an alternative. Here’s what those negated matchers might look like:
| RSpec::Matchers.define_negated_matcher :start_with_something_besides, |
| :start_with |
| |
| RSpec::Matchers.define_negated_matcher :end_with_something_besides, |
| :end_with |
Now, we can specify the exact behavior we want, with no ambiguity:
| # Strict: requires both conditions to be satisfied |
| expect('blazingly').to( |
| start_with_something_besides('a').and \ |
| end_with_something_besides('z') |
| ) |
| |
| # Permissive: requires at least one condition to be satisfied |
| expect('absolutely').to( |
| start_with_something_besides('a').or \ |
| end_with_something_besides('z') |
| ) |
The techniques we’ve seen so far—helper methods, matcher aliases, and negated matchers—are all about exposing existing matchers with new names. In the next section, we’ll take the next logical step: creating a new matcher that’s not based on an existing one.