RSpec stubs apparently not cleaned up after test - ruby

I've been working to diagnose a test failure that only occurs on my master branch. Following is the relevant code, in simplified form.
There's a service:
class Service
attr_reader :traces
def initialize
#traces = []
end
def do_work
#traces << Thread.current.backtrace
# ... actual work ...
end
end
And a class that makes use of the service:
class Widget
def get_cached_service
puts("Getting a cached service!")
puts("Do I already have one? #{!!#service}")
#service ||= make_service
end
def make_service
puts("Making a service!")
Service.new
end
end
I have a test (that lives in a file widget_spec.rb) that fails intermittently. This test creates an instance of Widget and calls get_cached_service. I see the Getting a cached service! message on the console, followed by Do I already have one? false, but I don't see the Making a service! message.
Furthermore, when I examine the traces attribute of the returned Service object, I find stack traces originating from other tests in my project (eg. foo_spec.rb, bar_spec.rb, etc).
In a few different places I find code like:
allow_any_instance_of(Widget)
.to receive(:make_service).and_return(whatever)
The other tests whose stack traces I find are likely stubbing make_service like this. But it appears that the stubbing is not being undone after those tests, as should always happen according to my understanding.
Is there any reason, other than a bug in rspec, that could cause a stub not to be reset at the end of a test?

The stub is almost certainly being cleared, but you’ve cached the fake instance in get_cached_service. Nothing clears the cached value in #service, and RSpec (rightfully) doesn’t know about it. As such, stubbing make_service is not enough if tests call get_cached_service. You have a few options:
Always stub get_cached_service instead of, or in addition to, make_service
Provide a way to clear the cached value which is called after each test.
Make the caching configurable in some way, or a wrapper around the actual implementation, such that the caching does not occur in test code.

I realise this is quite late to answer, but for posterity for anyone who reads this:
Use rspec bisect to figure out if there is a consistent test ordering that causes failure, then start ripping code out until you're left with only the bit that breaks.
I can't remember a case where RSpec is at fault - almost invariably, somewhere there is a class variable that isn't getting cleared, or someone is manually playing with a class with something like define_method. Occasionally it might be happening in a gem.
Make sure everything is cleared after every test in your spec_helper - clear the Rails cache, clear ActionMailer deliveries, return from Timecop freezes, etc.
Anything directly RSpec-related should clear itself in theory, because it's designed to integrate into RSpec, and is probably the least likely explanation in general.

Related

Rspec Double leaking to another example

I am testing a class that makes use of a client that makes external requests and I would like to mock this client, but verify that it gets called, however I am getting a double error.
My test looks like something like this:
describe '#execute' do
let(:attributes) { {foo: 'bar'} }
let(:client_double) { double('client', create: nil) }
let(:use_case) { described.class.new }
before do
allow(Client::Base).to receive(:new).and_return(client_double)
use_case.execute(attributes)
end
it 'creates something' do
expect(Something.find_by(foo: 'bar')).not_to be_nil
end
it 'calls client' do
expect(client).to have_received(:create).with('bar')
end
end
and the first example passes as expected, however rspec keeps breaking in the second example giving me this error:
#<Double "foo"> 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.
someone knows what I can do to fix it?
Reusing Fixtures with Let Methods
In this case, before is actually before(:each), which is reusing the client_double and attributes you defined with the #let helper method. The let commands make those variables functionally equivalent to instance variables within the scope of the described object, so you aren't really testing freshly-created objects in each example.
Some alternatives include:
Refactor to place all your setup into before(:each) without the let statements.
Make your tests DAMP by doing more setup within each example.
Set up new scope for a new #describe, so your test doubles/values aren't being reused.
Use your :before, :after, or :around blocks to reset state between tests, if needed.
Since you don't show the actual class or real code under test, it's hard to offer specific insights into the right way to test the object you're trying to test. It's not even clear why you feel you need to test the collaborator object within a unit test, so you might want to give some thought to that as well.
It turns out I was using a singleton as a client and haven't realized before, so it was trully class caching it through examples. To fix it all I did was mock the instantiate method instead of the new method and everything worked.
So in the end this worked:
allow(Client::Base).to receive(:instantiate).and_return(client_double)

How to stub method with specific parameter (and leave calls with other parameters unstubbed) in Mocha?

This question may seem like a duplicate of this one but the accepted answer does not help with my problem.
Context
Since Rails 5 no longer supports directly manipulating sessions in controller tests (which now inherit from ActionDispatch::IntegrationTest), I am going down the dark path of mocking and stubbing.
I know that this is bad practice and there are better ways to test a controller (and I do understand their move to integration tests) but I don't want to run a full integration test and call multiple actions in a single test just to set a specific session variable.
Scenario
Mocking/stubbing a session variable is actually quite easy with Mocha:
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")
Problem is, Rails stores a lot of things inside the session (just do a session.inspect anywhere in one of your views) and stubbing the :[] method obviously prevents access to any of them (so session[:some_other_variable] in a test will no longer work).
The question
Is there a way to stub/mock the :[] method only when called with a specific parameter and leave all other calls unstubbed?
I would have hoped for something like
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(anything).returns(original_value)
but I could not find a way to get it done.
By what I see, this is a feature not available in mocha
https://github.com/freerange/mocha/issues/334
I know this does exist in rspec-mock
https://github.com/rspec/rspec-mocks/blob/97c972be57f2c060a4a7fb8a3c5700a5ede693f0/spec/rspec/mocks/stub_implementation_spec.rb#L29
One hacky way that you an do it though, is to store the original session in an object, then mock that whenever a controller receives session, it returns another mock object, and in this you may either return a mocked velue, or delegate the call to the original session
class MySession
def initialize(original)
#original = original
end
def [](key)
if key == :mocked_key
2
else
original[key]
end
end
end
let!(original_session) { controller.send(:session) }
let(:my_session) { MySession.new(original_session) }
before do
controller.stubs(:session) { my_session }
end
Guess that mocha also allows you to do block mocking, so you don't need the class, but you need that original_session to be called
But I don't see a clean way

Mock a `puts` to a file in Rspec

I have a silly "queue-class[1]" with the following method, that I want to spec out with Rspec. I am not interested in testing if writing to the file-system works (It works, my computer works) but in whether or not the correct data gets written away.
def write transaction
File.open("messages/#{#next_id}", "w") {|f| f.puts transaction }
#next_id += 1
end
The spec for testing this is:
describe TransactionQueue do
context "#write" do
it "should write positive values" do
open_file = mock File
open_file.stub(:puts)
File.any_instance.stub(:open).and_yield(open_file)
File.any_instance.should_receive(:open)
open_file.should_receive(:puts).with("+100")
#queue = TransactionQueue.new
#queue.write("+100")
end
end
end
Running this, fails, because my Mocks never receive the expected "open" and "puts" messages.
Can I mock File this way? Did I use the any_instance correctly; is my attempt to stub a "block-yield" correct?
I'd rather not use extra gems like FakeFS when it can be avoided; this is not so much about getting it to work; bu mostly about actually understanding what is going on. Hence my attempt to avoid extra gems/layers of complexity.
[1] Class is from The Cucumber Book; but these tests have littel to do with Cucumber itself. I somehow broke the code when following the book; and want to find out what, by writing unit-tests for the parts that the book does not write tests for: the helper classes.
It's not "any instance" of the File class that you expect to receive the open method; it's the File class itself:
File.stub(:open).and_yield(open_file)
File.should_receive(:open)
Furthermore, don't use both a stub and an expectation. If you want to verify that File.open is actually called:
File.should_receive(:open).and_yield(open_file)
If you merely want to stub the open method in case it gets called, but don't want to require it as behaviour of the #queue.write method:
File.stub(:open).and_yield(open_file)
(This is from memory, I haven't used RSpec for a few months.)

RSpec any_instance return self

I'm trying to stub any instance of some class. I need to stub the fetch method, which fills the self with some data.
How can I get access to self variable, modify it and return on fetch method?
MyObject.any_instance.stub(:fetch) { self }
doesn't return a MyObject instance.
Maybe, mocks is more useful in this situation. Unfortunately, I haven't understood they yet.
There's an open rspec-mocks issue to address this. I hope to get around to addressing it at some point, but it's not simple to add this in a way that doesn't break existing spec suites that use any_instance with a block implementation, because we would start yielding an additional argument (e.g. the object instance).
Overall, any_instance can come in handy in some situations, but it's a bit of a smell, and you'll generally have fewer issues if you can find a way to mock or stub individual instances.
Here's a work around that I have not tested but should work:
orig_new = MyObject.method(:new)
MyObject.stub(:new) do |*args, &block|
orig_new.call(*args, &block).tap do |instance|
instance.stub(:fetch) { instance }
end
end
Essentially, we're simulating any_instance here by hooking into MyObject.new so that we can stub fetch on each new instance that is instantiated.
All that said, it's important to "listen to your tests", and, when something is hard to test, consider what that says about your design, rather than immediately using power tools like any_instance. Your original question doesn't give enough context for me to speculate anything about your design, but it's definitely where I would start when faced with a need to do this.
As far as I can see it, this doesn't seem to be possible, for some reason. I checked the current rspec-mocks implementation, and the method actually invoking the stub implementation seems to be the following:
# lib/rspec/mocks/message_expectation.rb:450
def call_implementation(*args, &block)
#implementation.arity == 0 ? #implementation.call(&block) : #implementation.call(*args, &block)
end
As it seems, the block is simply invoked by itself and not through instance_eval. Maybe there is another technique to achieve what you want though, after all I am not an RSpec expert by any means.

Should I open up a class's instance variables just for test validation?

I'm new to BDD, and I'm finding alot of instances where I'm adding instance variables to attr_accessor only so that my tests have an easy way to validate whether they are in the state that they should be in. But this feels a little dirty because no other class needs this information, and so I'm only making it public for the tests. Is this standard or a sign of bad design?
For example I have a collection class which stores an object, and then commits it to a batch array. At the end of the import, the batch array is used to make a batch insert into the database. But there is no need to check on the state of the batch at any point. But in testing I'm wanting to make sure that the batch is in the state that I think it is, and so am opening up that variable for inspection. Is the fact that it's not being checked within the code the real problem?
Is using instance_variable_get an option for you?
>> class Foo
.. def initialize
.. #foo = 'bar'
.. end
.. end #=> nil
>> Foo.new.instance_variable_get(:#foo) #=> "bar"
DON'T Do That!
This is going too deep. See the comments from #Andy and #Michael in the thread on the other 'accepted' answer
Don't write unit tests that look inside the code unit. If it's not something you can see from the external interfaces to the object then it doesn't need to be unit tested. You are going past how the code behaves into how it implements that behavior, and you don't want tests that go down to that level as they don't provide any real value in terms of proving that the code does what it should.
Just consider how many tests you might have to update if someone re-factors that code and in order to make it more readable changes the internal names of things.. or maybe finds a better way of doing whatever and all the instance variables get altered.. The code unit could still be working perfectly fine, but the unit tests would fail..
Alternatively if a change was made that adjusts how the internal variables report out to the interface, the code could effectively be broken, but the tests that are looking at the internal values of the instance variables would not report any failure.

Resources