rubyrspecrspec-expectations

Can I use a built-in RSpec matcher in a custom matcher?


I have the following expectation in a feature spec (pretty low-level but still necessary):

expect(Addressable::URI.parse(current_url).query_values).to include(
  'some => 'value',
  'some_other' => String
)

Note the second query value is a fuzzy match because I just want to make sure it's there but I can't be more specific about it.

I'd like to extract this into a custom matcher. I started with:

RSpec::Matchers.define :have_query_params do |expected_params|
  match do |url|
    Addressable::URI.parse(url).query_values == expected_params
  end
end

but this means I cannot pass {'some_other' => String} in there. To keep using a fuzzy match, I'd have to use the include matcher in my custom matcher.

However, anything within RSpec::Matchers::BuiltIn is marked as private API, and Include specifically is documented as:

# Provides the implementation for `include`.
# Not intended to be instantiated directly.

So, my question is: Is using a built-in matcher within a custom matcher supported in RSpec? How would I do that?


Solution

  • RSpec::Matchers appears to be a supported API (its rdoc doesn't say otherwise), so you can write your matcher in Ruby instead of in the matcher DSL (which is supported; see the second paragraph of the custom matcher documentation) and use RSpec::Matchers#include like this:

    spec/support/matchers.rb

    module My
      module Matchers
        def have_query_params(expected)
          HasQueryParams.new expected
        end
    
        class HasQueryParams
          include RSpec::Matchers
    
          def initialize(expected)
            @expected = expected
          end
    
          def matches?(url)
            actual = Addressable::URI.parse(url).query_values
            @matcher = include @expected
            @matcher.matches? actual
          end
    
          def failure_message
            @matcher.failure_message
          end
    
        end
      end
    end
    

    spec/support/matcher_spec.rb

    include My::Matchers
    
    describe My::Matchers::HasQueryParams do
      it "matches" do
        expect("http://example.com?a=1&b=2").to have_query_params('a' => '1', 'b' => '2')
      end
    end