Posted over 9 years ago. Visible to the public. Repeats. Linked content.

Write custom RSpec matchers

There are three ways to define your own RSpec matchers, with increasing complexibility and options:

1) Use RSpec::Matchers.define

Copy
RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end # optional failure_message do |actual| "expected that #{actual} would be a multiple of #{expected}" end # optional failure_message_when_negated do |actual| "expected that #{actual} would not be a multiple of #{expected}" end end

This is automatically available in all your specs when the code is loaded. We have a note on where to put custom RSpec matchers.

Chaining / fluent interfaces

Note that you can also make your matcher chainable, so a test can modifier its behavior. Using chaining you can write a matcher like this:

expect(x).to be_a_multiple_of(5).but_greater_than(2)

For more details see define matcher with a fluent interface from the RSpec docs.

2) Use matcher

To scope matchers to one or more example groups you can use the matcher method.

Copy
matcher :be_just_like do |expected| match {|actual| actual == expected} end

3) Use simple_matcher (deprecated)

Copy
module RhymeWithMatcher def rhyme_with(expected) simple_matcher do |given, matcher| matcher.description = "rhyme with #{expected.inspect}" matcher.failure_message = "expected #{given.inspect} to rhyme with #{expected.inspect}" matcher.negative_failure_message = "expected #{given.inspect} not to rhyme with #{expected.inspect}" given.rhymes_with? expected end end end ActiveSupport::TestCase.send :include, RhymeWithMatcher

4) Write a matcher class (for complex matchers)

Copy
module Aegis module Matchers class CheckPermissions def initialize(expected_resource, expected_options = {}) @expected_resource = expected_resource @expected_options = expected_options end def matches?(controller) @controller_class = controller.class @actual_resource = @controller_class.instance_variable_get('@aegis_permissions_resource') @actual_options = @controller_class.instance_variable_get('@aegis_permissions_options') @actual_resource == @expected_resource && @actual_options == @expected_options end def failure_message if @actual_resource != @expected_resource "expected #{@controller_class} to check permissions against resource #{@expected_resource.inspect}, but it checked against #{@actual_resource.inspect}" else "expected #{@controller_class} to check permissions with options #{@expected_options.inspect}, but options were #{@actual_options.inspect}" end end def negative_failure_message if @actual_resource == @expected_resource "expected #{@controller_class} to not check permissions against resource #{@expected_resource.inspect}" else "expected #{@controller_class} to not check permissions with options #{@expected_options.inspect}" end end def description description = "check permissions against resource #{@expected_resource.inspect}" description << " with options #{@expected_options.inspect}" if @expected_options.any? description end end def check_permissions(*args) CheckPermissions.new(*args) end end end

Register the matcher class with RSpec like this:

Copy
RSpec.configure do |config| config.include Aegis::Matchers end

In very old versions of Rails and RSpec you need to do this instead:

Copy
ActiveSupport::TestCase.send :include, Aegis::Matchers
Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Avatar
Henning Koch
Last edit:
4 months ago
by Emanuel De
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more