Custom Matchers

Paul Götze edited this page Aug 31, 2015 · 12 revisions

Writing custom matchers is dead simple now, with the new Matcher DSL released in rspec-1.2.0. We’ve made a couple of small changes, since its initial release, so what follows works with rspec-1.2.4 and up. Notes about previous versions below.

Start with an example you want to refactor

Let’s say you’ve got a code example like this:

describe 9 do
  it "should be a multiple of 3" do
    (9 % 3).should == 0
  end
end

What we really want to say here is that 9 should be a multiple of 3, like this:

describe 9 do
  it "should be a multiple of 3" do
    9.should be_a_multiple_of(3)
  end
end

Writing the matcher

Here’s how to write the matcher for this:

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end

That’s it!

How it works

The define() method defines a method named be_a_multiple_of(expected) that returns matcher object initialized with the expected value. So be_a_multiple_of(3) assigns 3 as the expected value.

The match method within the matcher definition block defines a match block. If called with the should() method, like so:

9.should be_a_multiple_of(3)

… then the expectation will pass if the block returns true. If the block returns false, the example will fail with an error message saying “expected 9 to be a multiple of 3”

If called with should_not(), like so:

9.should_not be_a_multiple_of(3)

… then the expectation will pass if the block returns false. If the block returns true, in this case, then the example will fail with an error message saying “expected 9 not to be a multiple of 3”

Auto-generated description/docstring

If you’re fond of writing your code examples like this:

describe 9 do
  it { should be_a_multiple_of(3) }
end

… you can do that, and the matcher will generate output like this:

9
- should be a multiple of 3

Custom failure messages

If you prefer to customize the failure messages, you can do so like this:

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end

  failure_message_for_should do |actual|
    "expected that #{actual} would be a precise multiple of #{expected}"
  end

  failure_message_for_should_not do |actual|
    "expected that #{actual} would not be a precise multiple of #{expected}"
  end

  description do
    "be a precise multiple of #{expected}"
  end
end

Helper Methods

Not much to say here: just write them:

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    do_the_math(actual, expected)
  end

  def do_the_math(actual, expected)
    actual % expected == 0
  end
end

Diffable

Coming soon …

Where to store them

You can store custom matcher definitions anywhere you want, but the convention is to keep them in files spec/support/matchers/ and then require them from spec/spec_helper.rb like this:

Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }

Limitations

Coming soon …

Changes since rspec-1.2.0

rspec-1.2.3 deprecates Spec::Matchers.create in favor of Spec::Matchers.define. The deprecated method will be removed from rspec-1.3. rspec-2 deprecates Spec::Matchers.define in favor of RSpec::Matchers.define.

rspec-1.2.3 introduced the diffable() method, so previous versions don’t support it.