How to write custom RSpec matchers in your Ruby on Rails app

Custom RSpec matchers are powerful tools that can significantly improve the readability and maintainability of your test suite. In this article, we'll explore how to create and implement custom matchers that make your specs more expressive and easier to understand.

Understanding the need for custom matchers

Let's start with a common scenario. Suppose you're testing a background color property:

it "has valid color" do
  background = 'green'
  expect(background).to eq('green')
end

While this works, we can make it more readable and reusable with custom matches. Please remember this is just an example; I know it's overkill here.

Creating your first custom matcher

Let's create a specialized matcher for checking if something is green:

# spec/support/matchers/be_green.rb
RSpec::Matchers.define :be_green do
  match do |actual|
    actual == 'green'
  end

  failure_message do |actual|
    "expected #{actual.inspect} to be 'green'"
  end

  failure_message_when_negated do |actual|
    "expected #{actual.inspect} not to be 'green'"
  end

  description do
    "be 'green'"
  end
end

Building flexible custom matchers

Sometimes we need more flexibility. Here's a more versatile color matcher:

# spec/support/matchers/has_color.rb
RSpec::Matchers.define :has_color do |expected|
  match do |actual|
    actual == expected
  end

  failure_message do |actual|
    "expected #{actual.inspect} to be '#{expected}'"
  end

  failure_message_when_negated do |actual|
    "expected #{actual.inspect} not to be '#{expected}'"
  end

  description do
    "has color '#{expected}'"
  end
end

Implementing custom matchers in tests

Here's how to use these matchers in your specs:

require_relative '../support/matchers/be_green' 
require_relative '../support/matchers/has_color' 
# or include these above in spec_helper.rb

RSpec.describe 'Color Tests' do
  let(:background) { 'green' }
  
  it 'has valid color' do
    expect(background).to be_green
  end

  it 'has valid color using our custom matcher' do
    expect(background).to has_color('green')
  end

  it 'return fail with error msg' do
    expect(background).to has_color('red')
  end
end

Summary

Custom RSpec matchers enhance test readability and maintainability by providing domain-specific language for your tests. They help create more expressive and cleaner test suites while maintaining clear failure messages. It's very useful.

Happy custom matching!