diff --git a/features/message_expectations/any_instance.feature b/features/message_expectations/any_instance.feature index dddecd4be..adb622ec1 100644 --- a/features/message_expectations/any_instance.feature +++ b/features/message_expectations/any_instance.feature @@ -2,6 +2,8 @@ Feature: expect a message on any instance of a class Use `any_instance.should_receive` to set an expectation that one (and only one) instance of a class receives a message before the example is completed. + + The spec will fail if no instance receives a message. Scenario: expect a message on any instance of a class Given a file named "example_spec.rb" with: diff --git a/features/method_stubs/any_instance.feature b/features/method_stubs/any_instance.feature index 9e09de87e..d70279e82 100644 --- a/features/method_stubs/any_instance.feature +++ b/features/method_stubs/any_instance.feature @@ -100,4 +100,34 @@ Feature: stub on any instance of a class end """ When I run `rspec example_spec.rb` - Then the examples should all pass \ No newline at end of file + Then the examples should all pass + + Scenario: stub a chain of methods an any instance + Given a file named "stub_chain_spec.rb" with: + """ + describe "stubbing a chain of methods" do + context "given symbols representing methods" do + it "returns the correct value" do + Object.any_instance.stub_chain(:one, :two, :three).and_return(:four) + Object.new.one.two.three.should eq(:four) + end + end + + context "given a hash at the end" do + it "returns the correct value" do + Object.any_instance.stub_chain(:one, :two, :three => :four) + Object.new.one.two.three.should eq(:four) + end + end + + context "given a string of methods separated by dots" do + it "returns the correct value" do + Object.any_instance.stub_chain("one.two.three").and_return(:four) + Object.new.one.two.three.should eq(:four) + end + end + end + """ + When I run `rspec stub_chain_spec.rb` + Then the examples should all pass + \ No newline at end of file diff --git a/lib/rspec/mocks/any_instance.rb b/lib/rspec/mocks/any_instance.rb index d8b81d5dc..fb1345030 100644 --- a/lib/rspec/mocks/any_instance.rb +++ b/lib/rspec/mocks/any_instance.rb @@ -1,5 +1,6 @@ require 'rspec/mocks/any_instance/chain' require 'rspec/mocks/any_instance/stub_chain' +require 'rspec/mocks/any_instance/stub_chain_chain' require 'rspec/mocks/any_instance/expectation_chain' require 'rspec/mocks/any_instance/message_chains' require 'rspec/mocks/any_instance/recorder' diff --git a/lib/rspec/mocks/any_instance/message_chains.rb b/lib/rspec/mocks/any_instance/message_chains.rb index 104972978..a62eff9bc 100644 --- a/lib/rspec/mocks/any_instance/message_chains.rb +++ b/lib/rspec/mocks/any_instance/message_chains.rb @@ -8,7 +8,7 @@ def add(method_name, chain) def remove_stub_chains_for!(method_name) chains = self[method_name] - chains.reject! { |chain| chain.is_a?(StubChain) } + chains.reject! { |chain| chain.is_a?(StubChain) || chain.is_a?(StubChainChain) } end def has_expectation?(method_name) diff --git a/lib/rspec/mocks/any_instance/recorder.rb b/lib/rspec/mocks/any_instance/recorder.rb index 43d12d900..d9b4217eb 100644 --- a/lib/rspec/mocks/any_instance/recorder.rb +++ b/lib/rspec/mocks/any_instance/recorder.rb @@ -36,6 +36,17 @@ def stub(method_name_or_method_map, *args, &block) end end + def stub_chain(method_name_or_string_chain, *args, &block) + if period_separated_method_chain?(method_name_or_string_chain) + first_method_name = method_name_or_string_chain.split('.').first.to_sym + else + first_method_name = method_name_or_string_chain + end + observe!(first_method_name) + message_chains.add(first_method_name, chain = StubChainChain.new(method_name_or_string_chain, *args, &block)) + chain + end + def should_receive(method_name, *args, &block) observe!(method_name) @expectation_set = true @@ -67,6 +78,10 @@ def verify end private + def period_separated_method_chain?(method_name) + method_name.is_a?(String) && method_name.include?('.') + end + def received_expected_message!(method_name) message_chains.received_expected_message!(method_name) restore_method!(method_name) diff --git a/lib/rspec/mocks/any_instance/stub_chain_chain.rb b/lib/rspec/mocks/any_instance/stub_chain_chain.rb new file mode 100644 index 000000000..5c8529538 --- /dev/null +++ b/lib/rspec/mocks/any_instance/stub_chain_chain.rb @@ -0,0 +1,34 @@ +module RSpec + module Mocks + module AnyInstance + class StubChainChain < Chain + def initialize(*args, &block) + record(:stub_chain, *args, &block) + end + + def invocation_order + @invocation_order ||= { + :stub_chain => [nil], + :and_return => [:stub_chain], + :and_raise => [:stub_chain], + :and_yield => [:stub_chain] + } + end + + def expectation_fulfilled? + true + end + + def expectation_fulfilled! + end + + private + def verify_invocation_order(rspec_method_name, *args, &block) + unless invocation_order[rspec_method_name].include?(last_message) + raise(NoMethodError, "Undefined method #{rspec_method_name}") + end + end + end + end + end +end \ No newline at end of file diff --git a/spec/rspec/mocks/any_instance_spec.rb b/spec/rspec/mocks/any_instance_spec.rb index b3db8d477..a04b64166 100644 --- a/spec/rspec/mocks/any_instance_spec.rb +++ b/spec/rspec/mocks/any_instance_spec.rb @@ -33,7 +33,13 @@ def another_existing_method; end lambda{ klass.any_instance.stub(:foo).and_yield(1).with("1") }.should raise_error(NoMethodError) end end - + + context "#stub_chain" do + it "raises an error if 'stub_chain' follows 'any_instance'" do + lambda{ klass.any_instance.and_return("1").stub_chain(:foo, :bar) }.should raise_error(NoMethodError) + end + end + context "#should_receive" do it "raises an error if 'should_receive' follows 'with'" do lambda{ klass.any_instance.with("1").should_receive(:foo) }.should raise_error(NoMethodError) @@ -50,7 +56,7 @@ def another_existing_method; end end end end - + context "with #stub" do it "does not suppress an exception when a method that doesn't exist is invoked" do klass.any_instance.stub(:foo) @@ -69,6 +75,23 @@ def another_existing_method; end Object.new.stub(:foo => 'foo', :bar => 'bar').should eq(:foo => 'foo', :bar => 'bar') klass.any_instance.stub(:foo => 'foo', :bar => 'bar').should eq(:foo => 'foo', :bar => 'bar') end + + context "allows a chain of methods to be stubbed using #stub_chain" do + it "given symbols representing the methods" do + klass.any_instance.stub_chain(:one, :two, :three).and_return(:four) + klass.new.one.two.three.should eq(:four) + end + + it "given a hash as the last argument uses the value as the expected return value" do + klass.any_instance.stub_chain(:one, :two, :three => :four) + klass.new.one.two.three.should eq(:four) + end + + it "given a string of '.' separated method names representing the chain" do + klass.any_instance.stub_chain('one.two.three').and_return(:four) + klass.new.one.two.three.should eq(:four) + end + end end context "behaves as 'every instance'" do