Permalink
Browse files

Got exactly one instance done except for never and any_number_of_times

  • Loading branch information...
1 parent 099b74e commit 9483c14766dc51599f627d41e336cec0c29555d1 @kaiwren kaiwren committed Mar 29, 2011
Showing with 214 additions and 50 deletions.
  1. +93 −15 lib/rspec/mocks/any_instance.rb
  2. +121 −35 spec/rspec/mocks/any_instance_spec.rb
View
108 lib/rspec/mocks/any_instance.rb
@@ -27,6 +27,17 @@ def playback!(instance)
end
end
+ def received_rspec_method?(rspec_method_name)
+ @messages.find do |message|
+ message.first.first == rspec_method_name
+ end
+ end
+
+ def expectation_fulfilled_at_least_once
+ # implement in subclasses
+ raise NotImplementedError
+ end
+
private
def verify_invocation_order(rspec_method_name, args, block)
# implement in subclasses
@@ -59,6 +70,10 @@ def initialize(*args, &block)
super(:stub, *args, &block)
end
+ def expectation_fulfilled_at_least_once
+ true
+ end
+
private
def verify_invocation_order(rspec_method_name, args, block)
if !invocation_order[rspec_method_name].include?(last_message)
@@ -79,8 +94,17 @@ def invocation_order
def initialize(*args, &block)
super(:should_receive, *args, &block)
+ @expectation_fulfilled = false
end
+ def expectation_fulfilled!
+ @expectation_fulfilled = true unless (received_rspec_method?(:never) || received_rspec_method?(:any_number_of_times))
+ end
+
+ def expectation_fulfilled_at_least_once
+ @expectation_fulfilled
+ end
+
private
def verify_invocation_order(rspec_method_name, args, block)
end
@@ -89,8 +113,10 @@ def verify_invocation_order(rspec_method_name, args, block)
class Recorder
def initialize(klass)
@observed_methods = {}
- @played_methods = []
+ @played_methods = {}
@klass = klass
+ @expectation_set = false
+ @expectation_fulfilled = false
end
def stub(method_name, *args, &block)
@@ -100,47 +126,81 @@ def stub(method_name, *args, &block)
def should_receive(method_name, *args, &block)
observe!(method_name)
+ @expectation_set = true
@observed_methods[method_name.to_sym] = ExpectationChain.new(method_name, *args, &block)
end
def stop_observing_currently_observed_methods!
observed_method_names.each do |method_name|
- stop_observing!(method_name)
+ restore_method!(method_name)
end
end
def playback_to_uninvoked_observed_methods_with_expectations!(instance)
@observed_methods.each do |method_name, chain|
case chain
when ExpectationChain
- chain.playback!(instance) unless @played_methods.include?(method_name)
+ chain.playback!(instance) unless @played_methods[method_name]
end
end
end
def playback!(instance, method_name)
RSpec::Mocks::space.add(instance) if RSpec::Mocks::space
@observed_methods[method_name].playback!(instance)
- @played_methods << method_name
+ @played_methods[method_name] = instance
+ received_message_for_a_method_with_an_expectation!(method_name) if has_expectation?(method_name)
end
+ def instance_that_received(method_name)
+ @played_methods[method_name]
+ end
+
+ def verify
+ if @expectation_set && !each_expectation_fulfilled_at_least_once?
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{methods_with_expectations.join(', ')}"
+ end
+ end
+
private
- def observed_method_names
- @observed_methods.keys
+ def each_expectation_fulfilled_at_least_once?
+ # @observed_methods.detect do |method_name, chain|
+ # chain.expectation_fulfilled? == false
+ # end
+ @expectation_fulfilled
end
-
- def build_alias_method_name(method_name)
- "__#{method_name}_without_any_instance__".to_sym
+
+ def has_expectation?(method_name)
+ @observed_methods[method_name].is_a?(ExpectationChain)
end
-
- def stop_observing!(method_name)
+
+ def methods_with_expectations
+ @observed_methods.map{|method_name, chain| method_name if chain.is_a?(ExpectationChain)}.compact
+ end
+
+ def received_message_for_a_method_with_an_expectation!(method_name)
+ @observed_methods[method_name].expectation_fulfilled!
+ @expectation_fulfilled = true
+ restore_method!(method_name)
+ mark_invoked!(method_name)
+ end
+
+ def restore_method!(method_name)
if @klass.method_defined?(build_alias_method_name(method_name))
restore_original_method!(method_name)
else
remove_dummy_method!(method_name)
end
@observed_methods.delete(method_name)
end
+
+ def observed_method_names
+ @observed_methods.keys
+ end
+
+ def build_alias_method_name(method_name)
+ "__#{method_name}_without_any_instance__".to_sym
+ end
def restore_original_method!(method_name)
alias_method_name = build_alias_method_name(method_name)
@@ -156,13 +216,17 @@ def remove_dummy_method!(method_name)
end
end
- def observe!(method_name)
+ def backup_method!(method_name)
alias_method_name = build_alias_method_name(method_name)
@klass.class_eval do
if method_defined?(method_name)
alias_method alias_method_name, method_name
end
end
+ end
+
+ def observe!(method_name)
+ backup_method!(method_name)
method = <<-EOM
def #{method_name}(*args, &blk)
self.class.__recorder.playback!(self, :#{method_name})
@@ -171,6 +235,19 @@ def #{method_name}(*args, &blk)
EOM
@klass.class_eval(method, __FILE__, __LINE__)
end
+
+ def mark_invoked!(method_name)
+ backup_method!(method_name)
+ method = <<-EOM
+ def #{method_name}(*args, &blk)
+ method_name = :#{method_name}
+ current_instance = self
+ invoked_instance = self.class.__recorder.instance_that_received(method_name)
+ raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
+ end
+ EOM
+ @klass.class_eval(method, __FILE__, __LINE__)
+ end
end
module ExpectationEnsurer
@@ -187,16 +264,17 @@ def any_instance
end
def rspec_verify
- super
+ value = super
+ __recorder.verify
+ value
ensure
rspec_reset
end
def rspec_reset
__recorder.stop_observing_currently_observed_methods!
@__recorder = nil
- response = super
- response
+ super
end
def reset?
View
156 spec/rspec/mocks/any_instance_spec.rb
@@ -8,6 +8,7 @@ class CustomErrorForAnyInstanceSpec < StandardError;end
let(:klass) do
Class.new do
def existing_method; 2; end
+ def another_existing_method; 4; end
end
end
@@ -145,6 +146,9 @@ class RSpec::SampleRspecTestClass;end
end
context "with #should_receive" do
+ let(:foo_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: foo' }
+ let(:existing_method_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: existing_method' }
+
context "when an expectation is set on a method does not exist" do
it "returns the expected value" do
klass.any_instance.should_receive(:foo).and_return(1)
@@ -154,16 +158,45 @@ class RSpec::SampleRspecTestClass;end
it "fails if an instance is created but no invocation occurs" do
expect do
klass.any_instance.should_receive(:foo)
- instance = klass.new
- instance.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ klass.new
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message)
end
+
+ context "number of instances" do
+ it "fails if no instance is created" do
+ expect do
+ klass.any_instance.should_receive(:foo).and_return(1)
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message)
+ end
- it "fails if no instance is created" do
- expect do
- klass.any_instance.should_receive(:foo).and_return(1)
- klass.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ it "fails if no instance is created and there are multiple expectations" do
+ expect do
+ klass.any_instance.should_receive(:foo)
+ klass.any_instance.should_receive(:bar)
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, 'Exactly one instance should have received the following message(s) but didn\'t: foo, bar')
+ end
+
+ context "after any one instance has received a message" do
+ it "passes if subsequent invocations do not receive that message" do
+ klass.any_instance.should_receive(:foo)
+ klass.new.foo
+ klass.new
+ end
+
+ it "fails if the method is invoked on a second instance" do
+ instance_one = klass.new
+ instance_two = klass.new
+ expect do
+ klass.any_instance.should_receive(:foo)
+
+ instance_one.foo
+ instance_two.foo
+ end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'foo' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}")
+ end
+ end
end
end
@@ -176,16 +209,45 @@ class RSpec::SampleRspecTestClass;end
it "fails if an instance is created but no invocation occurs" do
expect do
klass.any_instance.should_receive(:existing_method)
- instance = klass.new
- instance.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ klass.new
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, existing_method_expectation_error_message)
end
- it "fails if no instance is created" do
- expect do
- klass.any_instance.should_receive(:existing_method).and_return(1)
- klass.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ context "number of instances" do
+ it "fails if no instance is created" do
+ expect do
+ klass.any_instance.should_receive(:existing_method)
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, existing_method_expectation_error_message)
+ end
+
+ it "fails if no instance is created and there are multiple expectations" do
+ expect do
+ klass.any_instance.should_receive(:existing_method)
+ klass.any_instance.should_receive(:another_existing_method)
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, 'Exactly one instance should have received the following message(s) but didn\'t: existing_method, another_existing_method')
+ end
+
+ context "after any one instance has received a message" do
+ it "passes if subsequent invocations do not receive that message" do
+ klass.any_instance.should_receive(:existing_method)
+ klass.new.existing_method
+ klass.new
+ end
+
+ it "fails if the method is invoked on a second instance" do
+ instance_one = klass.new
+ instance_two = klass.new
+ expect do
+ klass.any_instance.should_receive(:existing_method)
+
+ instance_one.existing_method
+ instance_two.existing_method
+ end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'existing_method' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}")
+ end
+ end
end
end
@@ -202,23 +264,22 @@ class RSpec::SampleRspecTestClass;end
context "the 'once' constraint" do
it "passes for one invocation" do
klass.any_instance.should_receive(:foo).once
- instance = klass.new
- instance.foo
+ klass.new.foo
end
it "fails when no instances are declared" do
expect do
klass.any_instance.should_receive(:foo).once
- klass.new.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message)
end
it "fails when an instance is declared but there are no invocations" do
expect do
klass.any_instance.should_receive(:foo).once
- instance = klass.new
- klass.new.rspec_verify
- end.to raise_error(RSpec::Mocks::MockExpectationError)
+ klass.new
+ klass.rspec_verify
+ end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message)
end
it "fails for more than one invocation" do
@@ -322,9 +383,10 @@ class RSpec::SampleRspecTestClass;end
context "the 'never' constraint" do
it "passes for 0 invocations" do
- klass.any_instance.should_receive(:foo).never
- instance = klass.new
- instance.rspec_verify
+ pending do
+ klass.any_instance.should_receive(:foo).never
+ klass.rspec_verify
+ end
end
it "fails on the first invocation" do
@@ -337,6 +399,7 @@ class RSpec::SampleRspecTestClass;end
context "the 'any_number_of_times' constraint" do
it "passes for 0 invocations" do
+ pending
klass.any_instance.should_receive(:foo).any_number_of_times
klass.new.rspec_verify
end
@@ -352,19 +415,42 @@ class RSpec::SampleRspecTestClass;end
end
context "when resetting after an example" do
- it "restores the class to its original state after each example" do
- space = RSpec::Mocks::Space.new
- space.add(klass)
+ context "existing method" do
+ let(:space) { RSpec::Mocks::Space.new }
+
+ before(:each) do
+ space.add(klass)
+ klass.any_instance.stub(:existing_method).and_return(1)
+ klass.method_defined?(:__existing_method_without_any_instance__).should be_true
+ end
+
+ it "restores the class to its original state after each example when no instance is created" do
+ space.reset_all
+
+ klass.method_defined?(:__existing_method_without_any_instance__).should be_false
+ klass.new.existing_method.should eq(2)
+ end
- klass.any_instance.stub(:existing_method).and_return(1)
- klass.method_defined?(:__existing_method_without_any_instance__).should be_true
+ it "restores the class to its original state after each example when one instance is created" do
+ klass.new.existing_method
- space.reset_all
+ space.reset_all
- klass.method_defined?(:__existing_method_without_any_instance__).should be_false
- klass.new.existing_method.should eq(2)
- end
+ klass.method_defined?(:__existing_method_without_any_instance__).should be_false
+ klass.new.existing_method.should eq(2)
+ end
+ it "restores the class to its original state after each example when more than one instance is created" do
+ klass.new.existing_method
+ klass.new.existing_method
+
+ space.reset_all
+
+ klass.method_defined?(:__existing_method_without_any_instance__).should be_false
+ klass.new.existing_method.should eq(2)
+ end
+ end
+
it "adds an class to the current space when #any_instance is invoked" do
klass.any_instance
RSpec::Mocks::space.send(:mocks).should include(klass)

0 comments on commit 9483c14

Please sign in to comment.