Little Thing #1 — How do RSpec Matchers Work?

Danny Smith
Apr 12, 2018 · 2 min read

This is part of my Little Things series.

If you’ve ever written a test in RSpec, you’ve probably used this syntax for expectations:

expect(email).to be_truthy

How does it work, though? Can we replicate this with some simple ruby?

It’s actually not that complicated. We’re using two methods defined by RSpec here: expect and be_truthy.

The expect method takes any object as an argument and returns a ExpectationTarget object which stores the object you passed in and responds to exactly two methods: to and not_to. Both of these methods accept only an RSpec Matcher as their argument.

A Matcher isn’t special, it’s essentially just an object that responds to matches?.

Let’s start by building a matcher to check if something is truthy. It’s going to have one method, which takes a target argument, representing the object we’re testing against (in our original example, this is the email). If the target is truthy, we should return true from the method, otherwise we should return false.

class TruthyMatcher
def matches?(target)
!!target
end
end

Next, we need to define our ExpectationTarget class.

… which stores the object you passed in and responds to exactly two methods: to and not_to. Both of these methods accept only an RSpec Matcher as their argument.

We know that the to and not_to methods need to accept only a matcher, so we should check that whatever is passed to them responds to matches?. If it does, we should call it, passing in the object we stored. Finally, if the matcher returns true we should print a success message. If it returns false we should print a failure message.

We can now use this code like this:

email = 'bob@example.com'

All we need to get the behaviour we want is a couple of methods to wrap the classes we made:

def expect(target)
ExpectationTarget.new(target)
end

And there we have it:

email = 'bob@example.com'

We could go on to add an EqualityMatcher fairly easily too. This time, though, it needs to take an argument, and store it:

class EqualityMatcher
def initialize(object)
@object = object
end

Now we can use it like this:

email = 'bob@example.com'

I’ve obviously simplified things a bit so if you want to see how RSpec matchers actually work, or build your own to use in your ruby projects, take a look at the documentation.


Danny Smith

Written by

Software Engineer and Teacher. Passionate about technology education and helping people be happy at work. I also play blues music.