albertski
albertski

Reputation: 2622

Class has leaked into another example and can no longer be used in spec

I'm unable to replicate this locally, but for some reason I am getting the following error when running tests in CircleCi:

<Double Mylogger> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.

Here is a simplified version of my code:

# frozen_string_literal: true
describe 'my_rake_task' do
  let(:my_log) { Mylogger.new }

  subject { Rake::Task['my_rake_task'].execute }

  describe 'one' do
    context 'logs' do
      let(:logs) do
        [
          ['My message one'],
          ['My message two'],
        ]
      end

      after { subject }

      it 'correctly' do
        logs.each { |log| expect(my_log).to receive(:info).with(*log) }
      end
    end
  end

  describe 'two' do
    context 'logs' do
      let(:logs) do
        [
          ['My message three'],
          ['My message four'],
        ]
      end

      after { subject }

      it 'correctly' do
        logs.each { |log| expect(my_log).to receive(:info).with(*log) }
      end
    end
  end
end

Why is it saying MyLogger is a double? Why would it be leaking?

Upvotes: 2

Views: 960

Answers (2)

matthew.tuck
matthew.tuck

Reputation: 1317

Mostly commonly this sort of thing is caused by storing something in a class variable. You will have to figure out where that is, and how to clear it or avoid using a class variable - it could be in your class or in a gem.

Best practice, where using a class variable or an external system is causing inter-test issues, is to clean this sort of thing between tests, if possible. For example ActionMailer::Base.deliveries and Rails.cache are common things that should be cleared. You should also clear Faker::UniqueGenerator or RequestStore if you're using those gems, and I'm sure there are more.

Once you have found the class variable, if it's in your code, and you have determined a class variable is the correct approach, you can add a reset or clear class method to the class and call it in a before(:each) RSpec block in your spec_helper.rb or rails_helper.rb.

Note that while a lot of things will clear themselves automatically between tests (such as RSpec mocks), and make you think this is all automatic, in practice it is often anything but.

Tests will only remain independent by (a) mostly making use of objects created in the tests and mostly only storing data in there and (b) ensuring anything else is cleared between tests by your explicit code or within the responsible gem.

This can be especially annoying when dealing with external third-party systems, which rarely provide an API to clear a staging environment, and hence sometimes require considerable care even when using the vcr gem.

Upvotes: 1

Tony Arra
Tony Arra

Reputation: 11099

The reason that the error is saying that MyLogger is a double is because it is one. When you call expect(my_log).to receive or allow(my_log).to receive, you transform the instance into a partial-double.

As for why my_log is leaking: it's impossible to tell from the code that you posted. In order to cause a leak, some code either in your rake task or in the spec itself would need to be injecting my_log into some global state, like a class variable.

Upvotes: 1

Related Questions