Negating Matchers

In Putting the Pieces Together, we saw RSpec’s not_to and to_not methods, which specify that a given condition should not hold:

12-creating-custom-matchers/05/custom_matchers.rb
 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:

12-creating-custom-matchers/05/custom_matchers.rb
 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:

12-creating-custom-matchers/05/custom_matchers.rb
 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:

12-creating-custom-matchers/05/custom_matchers.rb
 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:

12-creating-custom-matchers/05/custom_matchers.rb
 # 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.