I'm getting intermittent test failures when using instance_double.
I have a file with 4 specs in it. Here is the source:
require 'rails_helper'
describe SubmitPost do
before(:each) do
@post = instance_double('Post')
allow(@post).to receive(:submitted_at=)
end
context 'on success' do
before(:each) do
allow(@post).to receive(:save).and_return(true)
@result = SubmitPost.call(post: @post)
end
it 'should set the submitted_at date' do
expect(@post).to have_received(:submitted_at=)
end
it 'should call save' do
expect(@post).to have_received(:save)
end
it 'should return success' do
expect(@result.success?).to eq(true)
expect(@result.failure?).to eq(false)
end
end
context 'on failure' do
before(:each) do
allow(@post).to receive(:save).and_return(false)
@result = SubmitPost.call(post: @post)
end
it 'should return failure' do
expect(@result.success?).to eq(false)
expect(@result.failure?).to eq(true)
end
end
end
This is a Rails 4.1.4 application. Internally, SubmitPost sets submitted_at and calls save on the passed-in Post. My Post model looks like this:
class Post < ActiveRecord::Base
validates :title, presence: true
validates :summary, presence: true
validates :url, presence: true
validates :submitted_at, presence: true
scope :chronological, -> { order('submitted_at desc') }
end
It's super vanilla.
When I run rake
, rspec
, or bin/rspec
, I get all all four tests failing 20% - 30% of the time. The error message is always:
Failure/Error: allow(@post).to receive(:submitted_at=)
Post does not implement: submitted_at=
If I label one of the specs with focus: true
, that one spec will fail 100% of the time.
If I replace instance_double
with double
, all specs will succeed 100% of the time.
It appears that instance_double is having some difficulty inferring the methods available on the Post class. It also appears to be somewhat random and timing-based.
Has anyone run into this issue? Any ideas what might be wrong? Any sense of how to go about troubleshooting this? Naturally, inserting a debugging breakpoint causes the specs to pass 100% of the time.
The problem you are seeing is that ActiveRecord creates column methods dynamically. instance_double
uses 'Post'
to look up methods to verify you are stubbing them correctly (unless the class doesn't exist yet or has not been loaded).
When a prior spec loads the model, ActiveRecord will create those dynamic methods so your spec passes as RSpec can then find the methods (with a respond_to?
call). When run in isolation the model hasn't been previously used and so ActiveRecord will not have created the dynamic methods yet and your test fails as you're experiencing.
The workaround for this is to force ActiveRecord to load the dynamic methods when they are called in your spec:
class Post < ActiveRecord::Base
def submitted_at=(value)
super
end
end
See the RSpec documentation for further explanation and workarounds for the problem:
https://www.relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/dynamic-classes
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With