Permalink
Browse files

some refactoring of any_instance

  • Loading branch information...
1 parent 7bf5851 commit ef65d360720b6b3d3d6c885024dbbc421efeeac0 @dchelimsky dchelimsky committed Apr 17, 2011
Showing with 54 additions and 73 deletions.
  1. +53 −72 lib/rspec/mocks/any_instance.rb
  2. +1 −1 lib/rspec/mocks/space.rb
@@ -2,60 +2,54 @@ module RSpec
module Mocks
module AnyInstance
class Chain
- def initialize(rspec_method_name, method_name, *args, &block)
- @messages = []
- record(rspec_method_name, [method_name] + args, block)
- end
-
[
:with, :and_return, :and_raise, :and_yield,
:once, :twice, :any_number_of_times,
:exactly, :times, :never,
:at_least, :at_most
].each do |method_name|
- dispatch_method_definition = <<-EOM
+ class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &block)
- record(:#{method_name}, args, block)
+ record(:#{method_name}, *args, &block)
end
EOM
- class_eval(dispatch_method_definition, __FILE__, __LINE__)
end
def playback!(instance)
- @messages.inject(instance) do |instance, message|
- instance.__send__(*message.first, &message.last)
+ messages.inject(instance) do |instance, message|
+ instance.send(*message.first, &message.last)
end
end
- def received_rspec_method?(rspec_method_name)
- !!@messages.find do |message|
- message.first.first == rspec_method_name
+ def constrained_to_any_of?(*constraints)
+ constraints.any? do |constraint|
+ messages.any? do |message|
+ message.first.first == constraint
+ end
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
- raise NotImplementedError
+ def messages
+ @messages ||= []
@kaiwren

kaiwren Apr 17, 2011

Contributor

Could you explain why one should favour this approach over initializing fields in the constructor? I think I get why, but I want to be sure so I'm not learning the right approach for the wrong reason.

@dchelimsky

dchelimsky Apr 17, 2011

Owner

I don't favor this approach in all cases, but in this case I wanted to have the subclasses call record directly rather than redirect to the super (felt more intention revealing to me), and this allowed me to avoid having initialize in the subs call super to initialize the messages object.

end
def last_message
- @messages.last.first.first unless @messages.empty?
+ messages.last.first.first unless messages.empty?
end
- def record(rspec_method_name, args, block)
- verify_invocation_order(rspec_method_name, args, block)
- @messages << [args.unshift(rspec_method_name), block]
+ def record(rspec_method_name, *args, &block)
+ verify_invocation_order(rspec_method_name, *args, &block)
+ messages << [args.unshift(rspec_method_name), block]
self
end
end
class StubChain < Chain
+ def initialize(*args, &block)
+ record(:stub, *args, &block)
+ end
+
def invocation_order
@invocation_order ||= {
:stub => [nil],
@@ -66,23 +60,24 @@ def invocation_order
}
end
- def initialize(*args, &block)
- super(:stub, *args, &block)
- end
-
- def expectation_fulfilled_at_least_once?
+ def expectation_filfilled?
true
end
private
- def verify_invocation_order(rspec_method_name, args, block)
- if !invocation_order[rspec_method_name].include?(last_message)
+ 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
class ExpectationChain < Chain
+ def initialize(*args, &block)
+ record(:should_receive, *args, &block)
+ @expectation_fulfilled = false
+ end
+
def invocation_order
@invocation_order ||= {
:should_receive => [nil],
@@ -92,21 +87,16 @@ def invocation_order
}
end
- def initialize(*args, &block)
- super(:should_receive, *args, &block)
- @expectation_fulfilled = false
- end
-
def expectation_fulfilled!
@expectation_fulfilled = true
end
- def expectation_fulfilled_at_least_once?
- (received_rspec_method?(:never) || received_rspec_method?(:any_number_of_times)) || @expectation_fulfilled
+ def expectation_filfilled?
+ @expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
end
private
- def verify_invocation_order(rspec_method_name, args, block)
+ def verify_invocation_order(rspec_method_name, *args, &block)
end
end
@@ -117,63 +107,61 @@ def initialize(klass)
@played_methods = {}
@klass = klass
@expectation_set = false
- @expectation_fulfilled = false
end
def stub(method_name, *args, &block)
- method_name_symbol = method_name.to_sym
- observe!(method_name_symbol)
- @message_chains[method_name_symbol] = StubChain.new(method_name_symbol, *args, &block)
+ observe!(method_name)
+ @message_chains[method_name] = StubChain.new(method_name, *args, &block)
end
def should_receive(method_name, *args, &block)
- method_name_symbol = method_name.to_sym
- observe!(method_name_symbol)
+ observe!(method_name)
@expectation_set = true
- @message_chains[method_name_symbol] = ExpectationChain.new(method_name, *args, &block)
+ @message_chains[method_name] = ExpectationChain.new(method_name, *args, &block)
end
- def stop_observing_currently_observed_methods!
+ def stop_observing!
@observed_methods.each do |method_name|
restore_method!(method_name)
end
end
def playback!(instance, method_name)
- RSpec::Mocks::space.add(instance) if RSpec::Mocks::space
+ RSpec::Mocks::space.add(instance)
@message_chains[method_name].playback!(instance)
@played_methods[method_name] = instance
- received_message_for_a_method_with_an_expectation!(method_name) if has_expectation?(method_name)
+ received_expected_message!(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_uninvoked_expectations.sort.join(', ')}"
+ if @expectation_set && !each_expectation_filfilled?
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
end
end
private
- def each_expectation_fulfilled_at_least_once?
- !@message_chains.find do |method_name, chain|
- not chain.expectation_fulfilled_at_least_once?
+ def each_expectation_filfilled?
+ @message_chains.all? do |method_name, chain|
+ chain.expectation_filfilled?
end
end
def has_expectation?(method_name)
@message_chains[method_name].is_a?(ExpectationChain)
end
- def methods_with_uninvoked_expectations
- @message_chains.map{|method_name, chain| method_name.to_s if chain.is_a?(ExpectationChain) && !chain.expectation_fulfilled_at_least_once? }.compact
+ def unfulfilled_expectations
+ @message_chains.map do |method_name, chain|
+ method_name.to_s if chain.is_a?(ExpectationChain) unless chain.expectation_filfilled?
+ end.compact
end
- def received_message_for_a_method_with_an_expectation!(method_name)
+ def received_expected_message!(method_name)
@message_chains[method_name].expectation_fulfilled!
- @expectation_fulfilled = true
restore_method!(method_name)
mark_invoked!(method_name)
end
@@ -187,7 +175,7 @@ def restore_method!(method_name)
end
def build_alias_method_name(method_name)
- "__#{method_name}_without_any_instance__".to_sym
+ "__#{method_name}_without_any_instance__"
end
def restore_original_method!(method_name)
@@ -216,52 +204,45 @@ def backup_method!(method_name)
def observe!(method_name)
@observed_methods << method_name
backup_method!(method_name)
- method = <<-EOM
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &blk)
self.class.__recorder.playback!(self, :#{method_name})
self.send(:#{method_name}, *args, &blk)
end
EOM
- @klass.class_eval(method, __FILE__, __LINE__)
end
def mark_invoked!(method_name)
backup_method!(method_name)
- method = <<-EOM
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
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
def any_instance
- RSpec::Mocks::space.add(self) if RSpec::Mocks::space
+ RSpec::Mocks::space.add(self)
__recorder
end
def rspec_verify
- value = super
__recorder.verify
- value
+ super
ensure
rspec_reset
end
def rspec_reset
- __recorder.stop_observing_currently_observed_methods!
+ __recorder.stop_observing!
@__recorder = nil
super
end
- def reset?
- !@__recorder && super
- end
-
def __recorder
@__recorder ||= AnyInstance::Recorder.new(self)
end
View
@@ -10,7 +10,7 @@ def verify_all
mock.rspec_verify
end
end
-
+
def reset_all
mocks.each do |mock|
mock.rspec_reset

0 comments on commit ef65d36

Please sign in to comment.