Flexible matcher class

Due to rspec-expectations, it is dead easy to define simple custom matcher in RSpec. The matchers even can be used in a concise macro like notation.
In the case the matcher logic complexity increases there is the approach to create a custom matcher class. That also applies for custom matchers that have to be available in other Ruby testing environments like MiniTest.

A class and its spec

First of all the starting point: a Product spec, which tests an alias:

# spec/product_spec.rb
require 'rails_helper'

RSpec.describe Product do
  subject { Product.new 'Car' }

  describe '#to_s' do
    it 'returns the name' do
      expect(subject.to_s).to eq(subject.name)
    end
  end
end

The product has a name and the alias to_s:

class Product
  attr_reader :name
  alias_method :name, :to_s

  def initialize(name)
    @name = name
  end
end

PORO matcher class

The matcher class is a PORO with at least 4 implemented methods:

  1. #description: the matcher description
  2. #failure_message: message for failing test (expect(subject).to match(something))
  3. #negative_failure_message: message for failing negated test (expect(subject).to_not match(something))
  4. #matches?: the actual comparison
# support/matchers/alias_method_matcher.rb
class AliasMethodMatcher
  def initialize(original_method)
    @original_method = original_method
  end

  def with(alias_method)
    @alias_method = alias_method
    self
  end

  def matches?(subject)
    raise 'No aliased method provided' if @alias_method.nil?
    subject.method(@original_method) == subject.method(@alias_method)
  end

  def failure_message
    "Expected ##{@original_method} to be aliased by ##{@alias_method}, but it is not"
  end

  def failure_message_when_negated
    "Expected ##{@original_method} to not be aliased by ##{@alias_method}, but it is"
  end

  def description
    "#{@alias_method} should be an alias of method #{@original_method}"
  end
end

The matcher benefits from a simple technique. Setter methods (with) are made chainable by returning the matcher object itself.
The comparing matches? is called at the very end of the chain automatically.
A helper function has to return the instantiated matcher object:

# support/matchers/alias_method_matcher.rb
def alias_method(method)
  AliasMethodMatcher.new(method)
end

The current Shoulda Matchers implementation also uses the same approach.
The same spec, but with the matcher:

# spec/product_spec.rb
require 'rails_helper'

RSpec.describe Product do
  subject { Product.new 'Car' }

  it { is_expected.to alias_method(:name).with(:to_s) }
end

The helper alias_method returns an instance of AliasMethodMatcher. The matcher object receives the message with with the argument to_s. This method also returns the matcher object (self). At the end of the it block, matches? is sent to the matcher object automatically and the comparison takes place.

The spec helper

In the case the custom matcher should not be required explicitly in each spec, it can be included globally in the spec helper:

# spec/spec_helper.rb
require File.join(File.dirname(__FILE__), 'support', 'alias_method_matcher.rb')

Many thanks to Arthur Shagall, who gave hints, how to improve the example.