Skip to content

Commit

Permalink
Issue rspec#70 add support for #stub_chain to any_instance
Browse files Browse the repository at this point in the history
  • Loading branch information
kaiwren committed Aug 14, 2011
1 parent 949c995 commit c6af9f9
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 4 deletions.
2 changes: 2 additions & 0 deletions features/message_expectations/any_instance.feature
Expand Up @@ -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:
Expand Down
32 changes: 31 additions & 1 deletion features/method_stubs/any_instance.feature
Expand Up @@ -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
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

1 change: 1 addition & 0 deletions 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'
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/any_instance/message_chains.rb
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions lib/rspec/mocks/any_instance/recorder.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions 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
27 changes: 25 additions & 2 deletions spec/rspec/mocks/any_instance_spec.rb
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit c6af9f9

Please sign in to comment.