Read more

Defining custom RSpec matchers

Avatar
Henning Koch
September 07, 2010Software engineer at makandra GmbH

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
Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

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 Show archive.org snapshot from the RSpec docs.

2) 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

3) 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

4) 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)
      CheckPermissions.new(*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
Avatar
Henning Koch
September 07, 2010Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2010-09-07 10:21)