Skip to content

Loading…

`any_instance` partial mocks aren't reset #416

Merged
merged 2 commits into from

3 participants

@JonRowe
RSpec member

As I summised in #399 partial mocks setup via the any_instance recorders aren't
reset by the conventional process, instead relying on their own verify process to
remove all methods setup by recorders. I'm not sure if this is an issue but this
demonstrates what I'm talking about.

Do not merge.

@JonRowe
RSpec member

@myronmarston did you get a chance to review this after merging the receive_messages stuff?

@myronmarston
RSpec member

Looking at it now for the first time :).

So basically the issue is the inconsistency/slight weirdness of the fact that proxy_for(obj).reset doesn't reset obj if it was stubbed via any_instance?

Seems like in practice, this isn't actually a problem for end users because RSpec resets the any_instance recorders as well, so the stubs get cleared between examples still, right?

I'd say that some simple refactorings that reduce/remove the inconsistencies would be nice but are definitely not a priority given the other things we have to work on and the fact that I can't think of a way for end-users to run into any bugs here.

@JonRowe
RSpec member

My concern was more that we check that mocks reset cleanly, but not the any instance ones. So far I haven't been able to force the any instance recorders to cleanup on the partial mocks either. (Generally not a problem between tests because instances don't survive across tests).

@myronmarston
RSpec member

My concern was more that we check that mocks reset cleanly, but not the any instance ones.

Sure we do:

https://github.com/rspec/rspec-mocks/blob/master/spec/rspec/mocks/any_instance_spec.rb#L711-L855

So far I haven't been able to force the any instance recorders to cleanup on the partial mocks either. (Generally not a problem between tests because instances don't survive across tests).

It doesn't cleanup with a simple reset partial_mock, but it should if you call RSpec::Mocks.space.verify_all.

@JonRowe
RSpec member

Ok I think it's useful to add these tests, so I've fixed them up to pass.

@coveralls

Coverage Status

Coverage decreased (-0.09%) when pulling 08c6637 on any_instance_resetting into fd78578 on master.

@JonRowe
RSpec member

Unless anyone objects these are good to merge...

@JonRowe JonRowe merged commit 331b4b3 into master

1 check passed

Details default The Travis CI build passed
@JonRowe JonRowe deleted the any_instance_resetting branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Showing with 52 additions and 0 deletions.
  1. +18 −0 spec/rspec/mocks/any_instance_spec.rb
  2. +34 −0 spec/rspec/mocks/matchers/receive_spec.rb
View
18 spec/rspec/mocks/any_instance_spec.rb
@@ -225,6 +225,24 @@ def private_method; :private_method_return_value; end
end
end
+ context "when partially mocking objects" do
+ let(:obj) { klass.new }
+
+ it "resets partially mocked objects correctly" do
+ allow_any_instance_of(klass).to receive(:existing_method).and_return("stubbed value")
+
+ # Simply resetting the proxy doesn't work
+ # what we need to have happen is
+ # ::RSpec::Mocks.any_instance_recorder_for(klass).stop_all_observation!
+ # but that is never invoked in ::
+ expect {
+ RSpec::Mocks.space.verify_all
+ }.to(
+ change { obj.existing_method }.from("stubbed value").to(:existing_method_return_value)
+ )
+ end
+ end
+
context "core ruby objects" do
it "works uniformly across *everything*" do
Object.any_instance.stub(:foo).and_return(1)
View
34 spec/rspec/mocks/matchers/receive_spec.rb
@@ -148,11 +148,36 @@ def receiver.method_missing(*a); end # a poor man's stub...
end
end
+ shared_examples_for "resets partial mocks cleanly" do
+ let(:klass) { Struct.new(:foo) }
+ let(:object) { klass.new :bar }
+
+ it "removes the method double" do
+ target.to receive(:foo).and_return(:baz)
+ expect { reset object }.to change { object.foo }.from(:baz).to(:bar)
+ end
+ end
+
+ shared_examples_for "resets partial mocks of any instance cleanly" do
+ let(:klass) { Struct.new(:foo) }
+ let(:object) { klass.new :bar }
+
+ it "removes the method double" do
+ target.to receive(:foo).and_return(:baz)
+ expect {
+ ::RSpec::Mocks.space.verify_all
+ }.to change { object.foo }.from(:baz).to(:bar)
+ end
+ end
+
describe "allow(...).to receive" do
include_examples "an expect syntax allowance" do
let(:receiver) { double }
let(:wrapped) { allow(receiver) }
end
+ include_examples "resets partial mocks cleanly" do
+ let(:target) { allow(object) }
+ end
end
describe "allow(...).not_to receive" do
@@ -167,6 +192,9 @@ def receiver.method_missing(*a); end # a poor man's stub...
let(:wrapped) { allow_any_instance_of(klass) }
let(:receiver) { klass.new }
end
+ include_examples "resets partial mocks of any instance cleanly" do
+ let(:target) { allow_any_instance_of(klass) }
+ end
end
describe "allow_any_instance_of(...).not_to receive" do
@@ -180,6 +208,9 @@ def receiver.method_missing(*a); end # a poor man's stub...
let(:receiver) { double }
let(:wrapped) { expect(receiver) }
end
+ include_examples "resets partial mocks cleanly" do
+ let(:target) { expect(object) }
+ end
end
describe "expect_any_instance_of(...).to receive" do
@@ -188,6 +219,9 @@ def receiver.method_missing(*a); end # a poor man's stub...
let(:wrapped) { expect_any_instance_of(klass) }
let(:receiver) { klass.new }
end
+ include_examples "resets partial mocks of any instance cleanly" do
+ let(:target) { expect_any_instance_of(klass) }
+ end
end
describe "expect(...).not_to receive" do
Something went wrong with that request. Please try again.