module RSpec::Matchers

RSpec::Matchers provides a number of useful matchers we use to define expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol) can be used as a matcher.

## Predicates

In addition to matchers that are defined explicitly, RSpec will create custom matchers on the fly for any arbitrary predicate, giving your specs a much more natural language feel.

A Ruby predicate is a method that ends with a “?” and returns true or false. Common examples are ‘empty?`, `nil?`, and `instance_of?`.

All you need to do is write ‘expect(..).to be_` followed by the predicate without the question mark, and RSpec will figure it out from there. For example:

expect([]).to be_empty     # => [].empty?() | passes
expect([]).not_to be_empty # => [].empty?() | fails

In addition to prefixing the predicate matchers with “be_”, you can also use “be_a_” and “be_an_”, making your specs read much more naturally:

expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes

expect(3).to be_a_kind_of(Integer)          # => 3.kind_of?(Numeric)     | passes
expect(3).to be_a_kind_of(Numeric)          # => 3.kind_of?(Numeric)     | passes
expect(3).to be_an_instance_of(Integer)     # => 3.instance_of?(Integer) | passes
expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails

RSpec will also create custom matchers for predicates like ‘has_key?`. To use this feature, just state that the object should have_key(:key) and RSpec will call has_key?(:key) on the target. For example:

expect(:a => "A").to have_key(:a)
expect(:a => "A").to have_key(:b) # fails

You can use this feature to invoke any predicate that begins with “has_”, whether it is part of the Ruby libraries (like ‘Hash#has_key?`) or a method you wrote on your own class.

Note that RSpec does not provide composable aliases for these dynamic predicate matchers. You can easily define your own aliases, though:

RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
expect(user_list).to include(a_user_who_is_an_admin)

## Alias Matchers

With {RSpec::Matchers.alias_matcher}, you can easily create an alternate name for a given matcher.

The description will also change according to the new name:

RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
sum_to(3).description # => "sum to 3"
a_list_that_sums_to(3).description # => "a list that sums to 3"

or you can specify a custom description like this:

RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
  description.sub("be sorted by", "a list sorted by")
end

be_sorted_by(:age).description # => "be sorted by age"
a_list_sorted_by(:age).description # => "a list sorted by age"

## Custom Matchers

When you find that none of the stock matchers provide a natural feeling expectation, you can very easily write your own using RSpec’s matcher DSL or writing one from scratch.

### Matcher DSL

Imagine that you are writing a game in which players can be in various zones on a virtual board. To specify that bob should be in zone 4, you could say:

expect(bob.current_zone).to eql(Zone.new("4"))

But you might find it more expressive to say:

expect(bob).to be_in_zone("4")

and/or

expect(bob).not_to be_in_zone("3")

You can create such a matcher like so:

RSpec::Matchers.define :be_in_zone do |zone|
  match do |player|
    player.in_zone?(zone)
  end
end

This will generate a be_in_zone method that returns a matcher with logical default messages for failures. You can override the failure messages and the generated description as follows:

RSpec::Matchers.define :be_in_zone do |zone|
  match do |player|
    player.in_zone?(zone)
  end

  failure_message do |player|
    # generate and return the appropriate string.
  end

  failure_message_when_negated do |player|
    # generate and return the appropriate string.
  end

  description do
    # generate and return the appropriate string.
  end
end

Each of the message-generation methods has access to the block arguments passed to the create method (in this case, zone). The failure message methods (failure_message and failure_message_when_negated) are passed the actual value (the receiver of expect(..) or expect(..).not_to).

### Custom Matcher from scratch

You could also write a custom matcher from scratch, as follows:

class BeInZone
  def initialize(expected)
    @expected = expected
  end

  def matches?(target)
    @target = target
    @target.current_zone.eql?(Zone.new(@expected))
  end

  def failure_message
    "expected #{@target.inspect} to be in Zone #{@expected}"
  end

  def failure_message_when_negated
    "expected #{@target.inspect} not to be in Zone #{@expected}"
  end
end

… and a method like this:

def be_in_zone(expected)
  BeInZone.new(expected)
end

And then expose the method to your specs. This is normally done by including the method and the class in a module, which is then included in your spec:

module CustomGameMatchers
  class BeInZone
    # ...
  end

  def be_in_zone(expected)
    # ...
  end
end

describe "Player behaviour" do
  include CustomGameMatchers
  # ...
end

or you can include in globally in a spec_helper.rb file required from your spec file(s):

RSpec::configure do |config|
  config.include(CustomGameMatchers)
end

### Making custom matchers composable

RSpec’s built-in matchers are designed to be composed, in expressions like:

expect(["barn", 2.45]).to contain_exactly(
  a_value_within(0.1).of(2.5),
  a_string_starting_with("bar")
)

Custom matchers can easily participate in composed matcher expressions like these. Include {RSpec::Matchers::Composable} in your custom matcher to make it support being composed (matchers defined using the DSL have this included automatically). Within your matcher’s ‘matches?` method (or the `match` block, if using the DSL), use `values_match?(expected, actual)` rather than `expected == actual`. Under the covers, `values_match?` is able to match arbitrary nested data structures containing a mix of both matchers and non-matcher objects. It uses `===` and `==` to perform the matching, considering the values to match if either returns `true`. The `Composable` mixin also provides some helper methods for surfacing the matcher descriptions within your matcher’s description or failure messages.

RSpec’s built-in matchers each have a number of aliases that rephrase the matcher from a verb phrase (such as ‘be_within`) to a noun phrase (such as `a_value_within`), which reads better when the matcher is passed as an argument in a composed matcher expressions, and also uses the noun-phrase wording in the matcher’s ‘description`, for readable failure messages. You can alias your custom matchers in similar fashion using {RSpec::Matchers.alias_matcher}.

## Negated Matchers

Sometimes if you want to test for the opposite using a more descriptive name instead of using ‘not_to`, you can use {RSpec::Matchers.define_negated_matcher}:

RSpec::Matchers.define_negated_matcher :exclude, :include
include(1, 2).description # => "include 1 and 2"
exclude(1, 2).description # => "exclude 1 and 2"

While the most obvious negated form may be to add a ‘not_` prefix, the failure messages you get with that form can be confusing (e.g. “expected [actual] to not [verb], but did not”). We’ve found it works best to find a more positive name for the negated form, such as ‘avoid_changing` rather than `not_change`.