Custom Matchers
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 it’s 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:
Spec::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:
Spec::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:
Spec::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-1.2.3 introduced the diffable() method, so previous versions don’t support it.
