A Custom “eventually” Rspec Matcher

Imagine you have a situation where some action can’t immediately be tested with an expectation. Perhaps, there’s some asynchronous code that must make a change and you want to test that the expectation is eventually true (likely within a few seconds). You can use this matcher I whipped up in your specs.

Copy and paste the following somewhere into your spec suite. `spec_helper` will work just fine.

RSpec::Matchers.define :eventually do |block|
  supports_block_expectations

  match do |actual|
    begin
      Timeout.timeout(5) do
        sleep 0.5 until block.call.matches?(actual.call)
        return true
      end
    rescue Timeout::Error
      return false
    end
  end

  failure_message do |_actual|
    block.call.failure_message
  end
end

Here’s an example of how you can use this matcher. Let’s say you want to test that a record of a sent email is created after a user is created. And that the email sending happens to be in a background job. You can write a spec that looks something like this:

it ‘eventually sends an email after user creation’ do
  User.create!
  expect { SentEmail.count  }.to eventually -> { eq 1  }
end

This is a contrived example, but you get the idea. What the ‘eventually’ matcher will do, as you can probably guess, is retry the underlying expectation `SentEmail.count eq 1` repeatedly, for a few seconds, until it either succeeds, or if not, it fails as if the underlying expectation failed.

I’ve gotten a little bit of mileage out of this. It works pretty well! If you have any improvements, let me know. Cheers!