An include_any Matcher for RSpec

Posted on 29 Jun 2008

Writing custom RSpec expectation matchers is a great way to enhance the readability of your specifications. As an example, here's a simple matcher to check whether a collection contains any of the given items. An example will make things clearer:

# Passing example
odds  = [1,3,5,7,9]
evens = [2,4,6,8]
odds.should_not include_any(*evens)

One thing to note is that include_any expects multiple arguments rather than a collection, hence the splat. Here's the code:

module Spec
  module Matchers
    def include_any(*args)
      IncludeAny.new(*args)
    end
  
    class IncludeAny
      def initialize(*args)
        @args = args
      end
    
      def matches?(target)
        @target = target
        @target.include_any?(*@args)
      end
    
      def failure_message
        "expected #{@target.inspect} to include one or more elements from #{@args.inspect}"
      end
    
      def negative_failure_message
        "expected #{@target.inspect} not to include any elements from #{@args.inspect}"
      end
    end
  end
end

This is about as trivial as a custom matcher gets. All of the work is done by the include_any? method, which doesn't actually exist as part of the core Ruby API. In fact it's a simple extension to Enumerable that I think I originally borrowed from Merb. Put the following in config/initializers/core_extensions.rb or similar to use it in a Rails app.

module Enumerable
  def include_any?(*args)
    args.any? { |arg| self.include?(arg) }
  end
end

Which custom matchers are you using?