Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec's instance_double creating intermittent spec failures

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.

like image 453
EricM Avatar asked Sep 10 '14 03:09

EricM


Video Answer


1 Answers

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

like image 133
PhilT Avatar answered Oct 30 '22 15:10

PhilT