-
-
Notifications
You must be signed in to change notification settings - Fork 358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix any_instance
so that it updates already stubbed instances
#651
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
module RSpec | ||
module Mocks | ||
module AnyInstance | ||
# @private | ||
# The `AnyInstance::Recorder` is responsible for redefining the klass's | ||
# instance method in order to add any stubs/expectations the first time | ||
# the method is called. It's not capable of updating a stub on an instance | ||
# that's already been previously stubbed (either directly, or via | ||
# `any_instance`). | ||
# | ||
# This proxy sits in front of the recorder and delegates both to it | ||
# and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed | ||
# instance of the class, in order to propogates changes to the instances. | ||
# | ||
# Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless | ||
# and is not persisted in `RSpec::Mocks.space`. | ||
# | ||
# Proxying for the message expectation fluent interface (typically chained | ||
# off of the return value of one of these methods) is provided by the | ||
# `FluentInterfaceProxy` class below. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. damn we have to do some terrible things to support this feature :( (I don't mean this solution is bad, I just mean :( that we have to do it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm totally on the same page :(. |
||
class Proxy | ||
def initialize(recorder, target_proxies) | ||
@recorder = recorder | ||
@target_proxies = target_proxies | ||
end | ||
|
||
def klass | ||
@recorder.klass | ||
end | ||
|
||
def stub(method_name_or_method_map, &block) | ||
if Hash === method_name_or_method_map | ||
method_name_or_method_map.each do |method_name, return_value| | ||
stub(method_name).and_return(return_value) | ||
end | ||
else | ||
perform_proxying(__method__, [method_name_or_method_map], block) do |proxy| | ||
proxy.add_stub(method_name_or_method_map, &block) | ||
end | ||
end | ||
end | ||
|
||
def unstub(method_name) | ||
perform_proxying(__method__, [method_name], nil) do |proxy| | ||
proxy.remove_stub_if_present(method_name) | ||
end | ||
end | ||
|
||
def stub_chain(*chain, &block) | ||
perform_proxying(__method__, chain, block) do |proxy| | ||
Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block) | ||
end | ||
end | ||
|
||
def expect_chain(*chain, &block) | ||
perform_proxying(__method__, chain, block) do |proxy| | ||
Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block) | ||
end | ||
end | ||
|
||
def should_receive(method_name, &block) | ||
perform_proxying(__method__, [method_name], block) do |proxy| | ||
proxy.add_message_expectation(method_name, &block) | ||
end | ||
end | ||
|
||
def should_not_receive(method_name, &block) | ||
perform_proxying(__method__, [method_name], block) do |proxy| | ||
proxy.add_message_expectation(method_name, &block).never | ||
end | ||
end | ||
|
||
private | ||
|
||
def perform_proxying(method_name, args, block, &target_proxy_block) | ||
recorder_value = @recorder.__send__(method_name, *args, &block) | ||
proxy_values = @target_proxies.map(&target_proxy_block) | ||
FluentInterfaceProxy.new([recorder_value] + proxy_values) | ||
end | ||
end | ||
|
||
# @private | ||
# Delegates messages to each of the given targets in order to | ||
# provide the fluent interface that is available off of message | ||
# expectations when dealing with `any_instance`. | ||
# | ||
# `targets` will typically contain 1 of the `AnyInstance::Recorder` | ||
# return values and N `MessageExpectation` instances (one per instance | ||
# of the `any_instance` klass). | ||
class FluentInterfaceProxy | ||
def initialize(targets) | ||
@targets = targets | ||
end | ||
|
||
if RUBY_VERSION.to_f > 1.8 | ||
def respond_to_missing?(method_name, include_private = false) | ||
super || @targets.first.respond_to?(method_name, include_private) | ||
end | ||
else | ||
def respond_to?(method_name, include_private = false) | ||
super || @targets.first.respond_to?(method_name, include_private) | ||
end | ||
end | ||
|
||
def method_missing(*args, &block) | ||
return_values = @targets.map { |t| t.__send__(*args, &block) } | ||
FluentInterfaceProxy.new(return_values) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,22 +30,19 @@ def self.enable_should(syntax_host = default_should_syntax_host) | |
syntax_host.class_exec do | ||
def should_receive(message, opts={}, &block) | ||
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) | ||
opts[:expected_from] ||= CallerFilter.first_non_rspec_line | ||
::RSpec::Mocks.expect_message(self, message, opts, &block) | ||
end | ||
|
||
def should_not_receive(message, &block) | ||
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) | ||
opts = {:expected_from => CallerFilter.first_non_rspec_line} | ||
::RSpec::Mocks.expect_message(self, message, opts, &block).never | ||
::RSpec::Mocks.expect_message(self, message, {}, &block).never | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you default the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure I follow... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The third parameter to ::RSpec::Mocks.expect_message(self, message, {}, &block).never vs ::RSpec::Mocks.expect_message(self, message, &block).never Just a very minor thing, simply a curiosity question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I now I see what you are getting at. I didn't realize that provided a default for the options hash. It's in a different file so I didn't notice -- I just updated based on what I saw in front of me at the call site. |
||
end | ||
|
||
def stub(message_or_hash, opts={}, &block) | ||
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) | ||
if ::Hash === message_or_hash | ||
message_or_hash.each {|message, value| stub(message).and_return value } | ||
else | ||
opts[:expected_from] = CallerFilter.first_non_rspec_line | ||
::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block) | ||
end | ||
end | ||
|
@@ -80,7 +77,7 @@ def received_message?(message, *args, &block) | |
Class.class_exec do | ||
def any_instance | ||
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__) | ||
::RSpec::Mocks.space.any_instance_recorder_for(self) | ||
::RSpec::Mocks.space.any_instance_proxy_for(self) | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I like this change, it really helped clear up what
*@expectation_args
was doing.