Today I Learned

hashrocket A Hashrocket project

RSpec Spies 🕵️‍♀️

Most of the time when I write an RSpec test to see if a message was received, I'll write the expectation first using a mock, then exercise the subject under test:

class SomeJob
  def perform
    SomeService.call
  end

end

Rspec.describe SomeJob do
  it "makes the call" do
    expect(SomeService).to receive(:call)
    SomeJob.new.perform
  end
end

Sometimes it's useful to flip this around, with the expectation after the action was performed. We can do this with a spy - we first stub the call with allow, exercise the subject under test, then assert with have_received:

class SomeJob
  attr_reader :some_attribute

  def perform
    set_an_instance_var
    SomeService.call(some_attribute)
  end

  def set_an_instance_var
    @some_attribute = :something
  end
end

Rspec.describe SomeJob do
  subject { SomeJob.new }

  it "makes the call with an argument" do
    allow(SomeService).to receive(:call)
    subject.perform
    expect(SomeService).to have_received(:call).with(subject.some_attribute)
  end
end

This is particularly useful if you want to assert against something (say an instance variable) that doesn't get set until the subject is exercised. There's no way to test the above example with the assertion first, since we won't know what some_attribute is until we perform - a perfect use case for a spy.

h/t Matt Polito

See More #ruby TILs
Looking for help? Each developer at Hashrocket has years of experience working with Ruby applications of all types and sizes. We're an active presence at Ruby conferences, have written some of the most popular gems, and have worked on many of the web's Ruby on Rails success stories. Contact us today to talk about your Ruby project.