Posted almost 11 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

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.

  1. Use matcher

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

matcher :be_just_like do |expected| match {|actual| actual == expected} end
  1. Use simple_matcher (deprecated)

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
  1. Write a matcher class (for complex matchers)

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)*args) end end end

Register the matcher class with RSpec like this:

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

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

ActiveSupport::TestCase.send :include, Aegis::Matchers

makandra has been working exclusively with Ruby on Rails since 2007. Our laser focus on a single technology has made us a leader in this space.

Owner of this card:

Henning Koch
Last edit:
almost 2 years 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 short-lived cookies to improve usability.
Accept or learn more