Doublespeak: Introduce a better MethodCall

MethodCall is used to represent a method call made on a double. It
stores the object the call was made on, the name of the method, any
arguments or block sent to the method, and the double itself. There were
quite a few places where we were using an (args, block) or (object,
args, block) tuple. We also already had a MethodCall class, and
additionally a MethodCallWithName class, and the two were basically the
same. These usages have all been collapsed.
This commit is contained in:
Elliot Winkler 2015-02-28 20:47:39 -07:00
parent 878cfbd4cc
commit 5e67a8305b
11 changed files with 123 additions and 71 deletions

View File

@ -215,7 +215,7 @@ module Shoulda
@double_collection =
Doublespeak.double_collection_for(::ActionController::Parameters)
@double_collection.register_stub(:require).to_return { |params| params }
@double_collection.register_stub(:require).to_return(&:object)
@double_collection.register_proxy(:permit)
end

View File

@ -21,8 +21,8 @@ end
require 'shoulda/matchers/doublespeak/double'
require 'shoulda/matchers/doublespeak/double_collection'
require 'shoulda/matchers/doublespeak/double_implementation_registry'
require 'shoulda/matchers/doublespeak/method_call'
require 'shoulda/matchers/doublespeak/object_double'
require 'shoulda/matchers/doublespeak/proxy_implementation'
require 'shoulda/matchers/doublespeak/structs'
require 'shoulda/matchers/doublespeak/stub_implementation'
require 'shoulda/matchers/doublespeak/world'

View File

@ -36,13 +36,13 @@ module Shoulda
end
end
def record_call(args, block)
calls << MethodCall.new(args, block)
def record_call(call)
calls << call
end
def call_original_method(object, args, block)
def call_original_method(call)
if original_method
original_method.bind(object).call(*args, &block)
original_method.bind(call.object).call(*call.args, &call.block)
end
end
@ -55,11 +55,19 @@ module Shoulda
end
def replace_method_with_double
implementation = @implementation
double = self
implementation = @implementation
_method_name = method_name
klass.__send__(:define_method, method_name) do |*args, &block|
implementation.call(double, self, args, block)
call = MethodCall.new(
double: double,
object: self,
method_name: _method_name,
args: args,
block: block
)
implementation.call(call)
end
end

View File

@ -0,0 +1,26 @@
module Shoulda
module Matchers
module Doublespeak
class MethodCall
attr_reader :method_name, :args, :block, :object, :double
def initialize(args)
@method_name = args.fetch(:method_name)
@args = args.fetch(:args)
@block = args[:block]
@double = args[:double]
@object = args[:object]
end
def ==(other)
other.is_a?(self.class) &&
method_name == other.method_name &&
args == other.args &&
block == other.block &&
double == other.double &&
object == other.object
end
end
end
end
end

View File

@ -19,8 +19,13 @@ module Shoulda
end
def method_missing(method_name, *args, &block)
calls << MethodCallWithName.new(method_name, args, block)
(calls_by_method_name[method_name] ||= []) << MethodCall.new(args, block)
call = MethodCall.new(
method_name: method_name,
args: args,
block: block
)
calls << call
(calls_by_method_name[method_name] ||= []) << call
nil
end

View File

@ -17,9 +17,9 @@ module Shoulda
@stub_implementation = stub_implementation
end
def call(double, object, args, block)
stub_implementation.call(double, object, args, block)
double.call_original_method(object, args, block)
def call(call)
stub_implementation.call(call)
call.double.call_original_method(call)
end
protected

View File

@ -1,10 +0,0 @@
module Shoulda
module Matchers
module Doublespeak
# @private
MethodCall = Struct.new(:args, :block)
# @private
MethodCallWithName = Struct.new(:method_name, :args, :block)
end
end
end

View File

@ -21,9 +21,9 @@ module Shoulda
end
end
def call(double, object, args, block)
double.record_call(args, block)
implementation.call(object, args, block)
def call(call)
call.double.record_call(call)
implementation.call(call)
end
protected

View File

@ -41,18 +41,26 @@ module Shoulda::Matchers::Doublespeak
describe '#activate' do
it 'replaces the method with an implementation' do
implementation = build_implementation
klass = create_class(a_method: 42)
method_name = :a_method
klass = create_class(method_name => :some_return_value)
instance = klass.new
double = described_class.new(klass, :a_method, implementation)
double = described_class.new(klass, method_name, implementation)
args = [:any, :args]
block = -> {}
call = MethodCall.new(
double: double,
object: instance,
method_name: method_name,
args: args,
block: block
)
double.activate
instance.a_method(*args, &block)
instance.__send__(method_name, *args, &block)
expect(implementation).
to have_received(:call).
with(double, instance, args, block)
with(call)
end
end
@ -92,29 +100,26 @@ module Shoulda::Matchers::Doublespeak
end
describe '#record_call' do
it 'stores the arguments and block given to the method in calls' do
double = described_class.new(:klass, :a_method, :implementation)
calls = [
[:any, :args], :block,
[:more, :args]
]
double.record_call(calls[0][0], calls[0][1])
double.record_call(calls[1][0], nil)
expect(double.calls[0].args).to eq calls[0][0]
expect(double.calls[0].block).to eq calls[0][1]
expect(double.calls[1].args).to eq calls[1][0]
it 'adds the given call to the list of calls' do
double = described_class.new(:a_klass, :a_method, :an_implementation)
double.record_call(:some_call)
expect(double.calls.last).to eq :some_call
end
end
describe '#call_original_method' do
it 'binds the stored method object to the class and calls it with the given args and block' do
it 'binds the stored method object to the given object and calls it with the given args and block' do
klass = create_class
instance = klass.new
actual_args = actual_block = method_called = nil
expected_args = [:one, :two, :three]
expected_block = -> { }
double = described_class.new(klass, :a_method, :implementation)
call = double('call',
object: instance,
args: expected_args,
block: expected_block
)
double = described_class.new(klass, :a_method, :an_implementation)
klass.__send__(:define_method, :a_method) do |*args, &block|
actual_args = expected_args
@ -123,7 +128,7 @@ module Shoulda::Matchers::Doublespeak
end
double.activate
double.call_original_method(instance, expected_args, expected_block)
double.call_original_method(call)
expect(expected_args).to eq actual_args
expect(expected_block).to eq actual_block
@ -131,11 +136,8 @@ module Shoulda::Matchers::Doublespeak
end
it 'does nothing if no method has been stored' do
double = described_class.new(:klass, :a_method, :implementation)
expect {
double.call_original_method(:instance, [:any, :args], nil)
}.not_to raise_error
double = described_class.new(:klass, :a_method, :an_implementation)
expect { double.call_original_method(:a_call) }.not_to raise_error
end
end

View File

@ -15,24 +15,28 @@ module Shoulda::Matchers::Doublespeak
describe '#call' do
it 'delegates to its stub_implementation' do
stub_implementation = build_stub_implementation
double = build_double
call = build_call
implementation = described_class.new(stub_implementation)
implementation.call(double, :object, :args, :block)
implementation.call(call)
expect(stub_implementation).
to have_received(:call).
with(double, :object, :args, :block)
with(call)
end
it 'calls #call_original_method on the double' do
stub_implementation = build_stub_implementation
implementation = described_class.new(stub_implementation)
double = build_double
implementation.call(double, :object, :args, :block)
call = build_call(double: double)
allow(double).to receive(:call_original_method).and_return(call)
implementation = described_class.new(stub_implementation)
implementation.call(call)
expect(double).
to have_received(:call_original_method).
with(:object, :args, :block)
with(call)
end
end
@ -43,5 +47,9 @@ module Shoulda::Matchers::Doublespeak
def build_double
double('double', call_original_method: nil)
end
def build_call(double: build_double)
double('call', double: double)
end
end
end

View File

@ -6,18 +6,20 @@ module Shoulda::Matchers::Doublespeak
it 'calls #record_call on the double' do
implementation = described_class.new
double = build_double
call = build_call(double: double)
allow(double).to receive(:record_call).with(:args, :block)
allow(double).to receive(:record_call).with(call)
implementation.call(double, :object, :args, :block)
implementation.call(call)
end
context 'if no explicit implementation was set' do
it 'returns nil' do
implementation = described_class.new
double = build_double
call = build_call(double: double)
return_value = implementation.call(double, :object, :args, :block)
return_value = implementation.call(call)
expect(return_value).to eq nil
end
@ -28,29 +30,33 @@ module Shoulda::Matchers::Doublespeak
implementation = described_class.new
implementation.returns(42)
double = build_double
call = build_call(double: double)
return_value = implementation.call(double, :object, :args, :block)
return_value = implementation.call(call)
expect(return_value).to eq 42
end
end
context 'if the implementation was set as a block' do
it 'calls the block with the object and args/block passed to the method' do
it 'calls the block with the MethodCall object the implementation was called with' do
double = build_double
expected_object, expected_args, expected_block = :object, :args, :block
call = build_call(
double: double,
object: expected_object,
args: expected_args,
block: expected_block
)
actual_object, actual_args, actual_block = []
implementation = described_class.new
implementation.returns do |object, args, block|
actual_object, actual_args, actual_block = object, args, block
implementation.returns do |actual_call|
actual_object = actual_call.object
actual_args = actual_call.args
actual_block = actual_call.block
end
implementation.call(
double,
expected_object,
expected_args,
expected_block
)
implementation.call(call)
expect(actual_object).to eq expected_object
expect(actual_args).to eq expected_args
@ -61,8 +67,9 @@ module Shoulda::Matchers::Doublespeak
implementation = described_class.new
implementation.returns { 42 }
double = build_double
call = build_call(double: double)
return_value = implementation.call(double, :object, :args, :block)
return_value = implementation.call(call)
expect(return_value).to eq 42
end
@ -73,8 +80,9 @@ module Shoulda::Matchers::Doublespeak
implementation = described_class.new
implementation.returns(:something_else) { 42 }
double = build_double
call = build_call(double: double)
return_value = implementation.call(double, :object, :args, :block)
return_value = implementation.call(call)
expect(return_value).to eq 42
end
@ -84,5 +92,10 @@ module Shoulda::Matchers::Doublespeak
def build_double
double('double', record_call: nil)
end
def build_call(options = {})
defaults = { double: build_double }
double('call', defaults.merge(options))
end
end
end