On Apr 26, 2011, at 5:39 PM, Matthew Van Horn wrote:
@my_foo ||= Foo.new
Foo.stub(:new => @stupid_mock)
# double "wtf" received unexpected message :do_something with (no args)
The only way I can think of RSpec not being affected by this is to reloading
all classes before each example, which would be both complicate and costly.
You’ll probably have to rethink your testing strategies, avoiding caching class
methods or not mocking something that could affect them, or initializing them
first before running the specs…
Best regards,
Rodrigo.
The example at the top of this thread fails the same way with
rspec-1.3.1, 1.3.2, and 2.5.0. I’m guessing that this was not the real
example that you saw passing in rspec-1 and failing in rspec-2, in which
case this example probably doesn’t trigger whatever differences you were
seeing between rspec-1 and rspec-2. Are you able to show us the real
code?
I see now the crux of the issue - mocks are implicitly cleared out before each
example, so even though the same object is being returned, RSpec is sneakily
substituting a doppelganger for @stupid_mock.
RSpec is doing nothing sneaky. The code in the before hook assigns a new
double to the @stupid_mock instance variable for each example, but the
my_foo method memoizes the @stupid_mock from the first example at the
class level, so it returns that instance for all subsequent requests in
the same runtime.
This apparently wasn’t the case in the last version, where the instance variable
would continue to be connected to the same object.
before(:each) blocks have been run before each example since they were
first introduced, so this is not accurate. Whatever behavior you were
seeing was not due to RSpec keeping instance variable assignments across
examples.
This might be intended behavior, but it feels like a bug because that memoizing
behavior is so common.
Agreed it is common, but the common approach to stubbing things that are
memoized is to stub the method (in this case my_foo).
Because I can’t set up the double in a before(:all) block due to the above
behavior, the solution is to add:
Bar.instance_variable_set(:@my_foo, nil) # Forcibly clear Bar’s cached
instance
to the before(:each) block.
I’d recommend you just stub the my_foo method instead. You said you
prefer to avoid doing that earlier this thread, but I think it’s a
cleaner solution as it is isolated to the current example. Keep in mind
that if the double ends up the memoized value for my_foo, it will remain
so for the rest of the suite, whereas if you just stub my_foo for the
examples you need the double, it will be limited to those.
HTH,
David