mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Add a small stubbing library
This provides a robust solution for temporarily stubbing (and unstubbing) methods. It will be internally by the strong parameters and delegation matchers.
This commit is contained in:
parent
c7b1505990
commit
dfebd81af0
18 changed files with 904 additions and 0 deletions
|
@ -1,4 +1,5 @@
|
|||
require 'shoulda/matchers/assertion_error'
|
||||
require 'shoulda/matchers/doublespeak'
|
||||
require 'shoulda/matchers/error'
|
||||
require 'shoulda/matchers/rails_shim'
|
||||
require 'shoulda/matchers/warn'
|
||||
|
|
27
lib/shoulda/matchers/doublespeak.rb
Normal file
27
lib/shoulda/matchers/doublespeak.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'forwardable'
|
||||
|
||||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class << self
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :world, :register_double_collection,
|
||||
:with_doubles_activated
|
||||
|
||||
def world
|
||||
@_world ||= World.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'shoulda/matchers/doublespeak/double'
|
||||
require 'shoulda/matchers/doublespeak/double_collection'
|
||||
require 'shoulda/matchers/doublespeak/double_implementation_registry'
|
||||
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'
|
74
lib/shoulda/matchers/doublespeak/double.rb
Normal file
74
lib/shoulda/matchers/doublespeak/double.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class Double
|
||||
attr_reader :calls
|
||||
|
||||
def initialize(klass, method_name, implementation)
|
||||
@klass = klass
|
||||
@method_name = method_name
|
||||
@implementation = implementation
|
||||
@activated = false
|
||||
@calls = []
|
||||
end
|
||||
|
||||
def to_return(value = nil, &block)
|
||||
if block
|
||||
implementation.returns(&block)
|
||||
else
|
||||
implementation.returns(value)
|
||||
end
|
||||
end
|
||||
|
||||
def activate
|
||||
unless @activated
|
||||
store_original_method
|
||||
replace_method_with_double
|
||||
@activated = true
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate
|
||||
if @activated
|
||||
restore_original_method
|
||||
@activated = false
|
||||
end
|
||||
end
|
||||
|
||||
def record_call(args, block)
|
||||
calls << MethodCall.new(args, block)
|
||||
end
|
||||
|
||||
def call_original_method(object, args, block)
|
||||
if original_method
|
||||
original_method.bind(object).call(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :klass, :method_name, :implementation, :original_method
|
||||
|
||||
def store_original_method
|
||||
@original_method = klass.instance_method(method_name)
|
||||
end
|
||||
|
||||
def replace_method_with_double
|
||||
implementation = @implementation
|
||||
double = self
|
||||
|
||||
klass.__send__(:define_method, method_name) do |*args, &block|
|
||||
implementation.call(double, self, args, block)
|
||||
end
|
||||
end
|
||||
|
||||
def restore_original_method
|
||||
original_method = @original_method
|
||||
klass.__send__(:define_method, method_name) do |*args, &block|
|
||||
original_method.bind(self).call(*args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
54
lib/shoulda/matchers/doublespeak/double_collection.rb
Normal file
54
lib/shoulda/matchers/doublespeak/double_collection.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class DoubleCollection
|
||||
def initialize(klass)
|
||||
@klass = klass
|
||||
@doubles_by_method_name = {}
|
||||
end
|
||||
|
||||
def register_stub(method_name)
|
||||
register_double(method_name, :stub)
|
||||
end
|
||||
|
||||
def register_proxy(method_name)
|
||||
register_double(method_name, :proxy)
|
||||
end
|
||||
|
||||
def activate
|
||||
doubles_by_method_name.each do |method_name, double|
|
||||
double.activate
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate
|
||||
doubles_by_method_name.each do |method_name, double|
|
||||
double.deactivate
|
||||
end
|
||||
end
|
||||
|
||||
def calls_to(method_name)
|
||||
double = doubles_by_method_name[method_name]
|
||||
|
||||
if double
|
||||
double.calls
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :klass, :doubles_by_method_name
|
||||
|
||||
def register_double(method_name, implementation_type)
|
||||
implementation =
|
||||
DoubleImplementationRegistry.find(implementation_type)
|
||||
double = Double.new(klass, method_name, implementation)
|
||||
doubles_by_method_name[method_name] = double
|
||||
double
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
module DoubleImplementationRegistry
|
||||
class << self
|
||||
REGISTRY = {}
|
||||
|
||||
def find(type)
|
||||
find_class!(type).create
|
||||
end
|
||||
|
||||
def register(klass, type)
|
||||
REGISTRY[type] = klass
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_class!(type)
|
||||
REGISTRY.fetch(type) do
|
||||
raise ArgumentError, "No double implementation class found for '#{type}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
lib/shoulda/matchers/doublespeak/object_double.rb
Normal file
32
lib/shoulda/matchers/doublespeak/object_double.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class ObjectDouble < BasicObject
|
||||
attr_reader :calls
|
||||
|
||||
def initialize
|
||||
@calls = []
|
||||
@calls_by_method_name = {}
|
||||
end
|
||||
|
||||
def calls_to(method_name)
|
||||
@calls_by_method_name[method_name] || []
|
||||
end
|
||||
|
||||
def respond_to?(name, include_private = nil)
|
||||
true
|
||||
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)
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :calls_by_method_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
lib/shoulda/matchers/doublespeak/proxy_implementation.rb
Normal file
30
lib/shoulda/matchers/doublespeak/proxy_implementation.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class ProxyImplementation
|
||||
extend Forwardable
|
||||
|
||||
DoubleImplementationRegistry.register(self, :proxy)
|
||||
|
||||
def_delegators :stub_implementation, :returns
|
||||
|
||||
def self.create
|
||||
new(StubImplementation.new)
|
||||
end
|
||||
|
||||
def initialize(stub_implementation)
|
||||
@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)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :stub_implementation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/shoulda/matchers/doublespeak/structs.rb
Normal file
8
lib/shoulda/matchers/doublespeak/structs.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
MethodCall = Struct.new(:args, :block)
|
||||
MethodCallWithName = Struct.new(:method_name, :args, :block)
|
||||
end
|
||||
end
|
||||
end
|
34
lib/shoulda/matchers/doublespeak/stub_implementation.rb
Normal file
34
lib/shoulda/matchers/doublespeak/stub_implementation.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class StubImplementation
|
||||
DoubleImplementationRegistry.register(self, :stub)
|
||||
|
||||
def self.create
|
||||
new
|
||||
end
|
||||
|
||||
def initialize
|
||||
@implementation = proc { nil }
|
||||
end
|
||||
|
||||
def returns(value = nil, &block)
|
||||
if block
|
||||
@implementation = block
|
||||
else
|
||||
@implementation = proc { value }
|
||||
end
|
||||
end
|
||||
|
||||
def call(double, object, args, block)
|
||||
double.record_call(args, block)
|
||||
implementation.call(object, args, block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :implementation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
38
lib/shoulda/matchers/doublespeak/world.rb
Normal file
38
lib/shoulda/matchers/doublespeak/world.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module Doublespeak
|
||||
class World
|
||||
def register_double_collection(klass)
|
||||
double_collection = DoubleCollection.new(klass)
|
||||
double_collections_by_class[klass] = double_collection
|
||||
double_collection
|
||||
end
|
||||
|
||||
def with_doubles_activated
|
||||
activate
|
||||
yield
|
||||
ensure
|
||||
deactivate
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def activate
|
||||
double_collections_by_class.each do |klass, double_collection|
|
||||
double_collection.activate
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate
|
||||
double_collections_by_class.each do |klass, double_collection|
|
||||
double_collection.deactivate
|
||||
end
|
||||
end
|
||||
|
||||
def double_collections_by_class
|
||||
@_double_collections_by_class ||= {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
102
spec/shoulda/matchers/doublespeak/double_collection_spec.rb
Normal file
102
spec/shoulda/matchers/doublespeak/double_collection_spec.rb
Normal file
|
@ -0,0 +1,102 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe DoubleCollection do
|
||||
describe '#register_stub' do
|
||||
it 'calls DoubleImplementationRegistry.find correctly' do
|
||||
double_collection = described_class.new(:klass)
|
||||
DoubleImplementationRegistry.expects(:find).with(:stub)
|
||||
double_collection.register_stub(:a_method)
|
||||
end
|
||||
|
||||
it 'calls Double.new correctly' do
|
||||
DoubleImplementationRegistry.stubs(:find).returns(:implementation)
|
||||
double_collection = described_class.new(:klass)
|
||||
Double.expects(:new).with(:klass, :a_method, :implementation)
|
||||
double_collection.register_stub(:a_method)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#register_proxy' do
|
||||
it 'calls DoubleImplementationRegistry.find correctly' do
|
||||
double_collection = described_class.new(:klass)
|
||||
DoubleImplementationRegistry.expects(:find).with(:proxy)
|
||||
double_collection.register_proxy(:a_method)
|
||||
end
|
||||
|
||||
it 'calls Double.new correctly' do
|
||||
DoubleImplementationRegistry.stubs(:find).returns(:implementation)
|
||||
double_collection = described_class.new(:klass)
|
||||
Double.expects(:new).with(:klass, :a_method, :implementation)
|
||||
double_collection.register_proxy(:a_method)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#activate' do
|
||||
it 'replaces all registered methods with doubles' do
|
||||
klass = create_class(first_method: 1, second_method: 2)
|
||||
double_collection = described_class.new(klass)
|
||||
double_collection.register_stub(:first_method)
|
||||
double_collection.register_stub(:second_method)
|
||||
|
||||
double_collection.activate
|
||||
|
||||
instance = klass.new
|
||||
expect(instance.first_method).to eq nil
|
||||
expect(instance.second_method).to eq nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deactivate' do
|
||||
it 'restores the original methods that were doubled' do
|
||||
klass = create_class(first_method: 1, second_method: 2)
|
||||
double_collection = described_class.new(klass)
|
||||
double_collection.register_stub(:first_method)
|
||||
double_collection.register_stub(:second_method)
|
||||
|
||||
double_collection.activate
|
||||
double_collection.deactivate
|
||||
|
||||
instance = klass.new
|
||||
expect(instance.first_method).to eq 1
|
||||
expect(instance.second_method).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#calls_to' do
|
||||
it 'returns all calls to the given method' do
|
||||
klass = create_class(a_method: nil)
|
||||
double_collection = described_class.new(klass)
|
||||
double_collection.register_stub(:a_method)
|
||||
double_collection.activate
|
||||
|
||||
actual_calls = [
|
||||
{ args: [:some, :args, :here] },
|
||||
{ args: [:some, :args], block: -> { :whatever } }
|
||||
]
|
||||
instance = klass.new
|
||||
instance.a_method(*actual_calls[0][:args])
|
||||
instance.a_method(*actual_calls[1][:args], &actual_calls[1][:block])
|
||||
|
||||
calls = double_collection.calls_to(:a_method)
|
||||
expect(calls[0].args).to eq actual_calls[0][:args]
|
||||
expect(calls[1].args).to eq actual_calls[1][:args]
|
||||
expect(calls[1].block).to eq actual_calls[1][:block]
|
||||
end
|
||||
|
||||
it 'returns an empty array if the method has never been doubled' do
|
||||
klass = create_class
|
||||
double_collection = described_class.new(klass)
|
||||
expect(double_collection.calls_to(:non_existent_method)).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
def create_class(methods = {})
|
||||
Class.new.tap do |klass|
|
||||
methods.each do |name, value|
|
||||
klass.__send__(:define_method, name) { |*args| value }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe DoubleImplementationRegistry do
|
||||
describe '.find' do
|
||||
it 'returns an instance of StubImplementation if given :stub' do
|
||||
expect(described_class.find(:stub)).to be_a(StubImplementation)
|
||||
end
|
||||
|
||||
it 'returns ProxyImplementation if given :proxy' do
|
||||
expect(described_class.find(:proxy)).to be_a(ProxyImplementation)
|
||||
end
|
||||
|
||||
it 'raises an ArgumentError if not given a registered implementation' do
|
||||
expect {
|
||||
expect(described_class.find(:something_else))
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
144
spec/shoulda/matchers/doublespeak/double_spec.rb
Normal file
144
spec/shoulda/matchers/doublespeak/double_spec.rb
Normal file
|
@ -0,0 +1,144 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe Double do
|
||||
describe '#to_return' do
|
||||
it 'tells its implementation to call the given block' do
|
||||
sent_block = -> { }
|
||||
actual_block = nil
|
||||
implementation = stub
|
||||
implementation.singleton_class.__send__(:define_method, :returns) do |&block|
|
||||
actual_block = block
|
||||
end
|
||||
double = described_class.new(:klass, :a_method, implementation)
|
||||
double.to_return(&sent_block)
|
||||
expect(actual_block).to eq sent_block
|
||||
end
|
||||
|
||||
it 'tells its implementation to return the given value' do
|
||||
implementation = mock()
|
||||
implementation.expects(:returns).with(:implementation)
|
||||
double = described_class.new(:klass, :a_method, implementation)
|
||||
double.to_return(:implementation)
|
||||
end
|
||||
|
||||
it 'prefers a block over a non-block' do
|
||||
sent_block = -> { }
|
||||
actual_block = nil
|
||||
implementation = stub
|
||||
implementation.singleton_class.__send__(:define_method, :returns) do |&block|
|
||||
actual_block = block
|
||||
end
|
||||
double = described_class.new(:klass, :a_method, implementation)
|
||||
double.to_return(:value, &sent_block)
|
||||
expect(actual_block).to eq sent_block
|
||||
end
|
||||
end
|
||||
|
||||
describe '#activate' do
|
||||
it 'replaces the method with an implementation' do
|
||||
implementation = stub
|
||||
klass = create_class(a_method: 42)
|
||||
instance = klass.new
|
||||
double = described_class.new(klass, :a_method, implementation)
|
||||
args = [:any, :args]
|
||||
block = -> {}
|
||||
implementation.expects(:call).with(double, instance, args, block)
|
||||
|
||||
double.activate
|
||||
instance.a_method(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deactivate' do
|
||||
it 'restores the original method after being doubled' do
|
||||
implementation = stub(call: nil)
|
||||
klass = create_class(a_method: 42)
|
||||
instance = klass.new
|
||||
double = described_class.new(klass, :a_method, implementation)
|
||||
|
||||
double.activate
|
||||
double.deactivate
|
||||
expect(instance.a_method).to eq 42
|
||||
end
|
||||
|
||||
it 'still restores the original method if #activate was called twice' do
|
||||
implementation = stub(call: nil)
|
||||
klass = create_class(a_method: 42)
|
||||
instance = klass.new
|
||||
double = described_class.new(klass, :a_method, implementation)
|
||||
|
||||
double.activate
|
||||
double.activate
|
||||
double.deactivate
|
||||
expect(instance.a_method).to eq 42
|
||||
end
|
||||
|
||||
it 'does nothing if the method has not been doubled' do
|
||||
implementation = stub(call: nil)
|
||||
klass = create_class(a_method: 42)
|
||||
instance = klass.new
|
||||
double = described_class.new(klass, :a_method, implementation)
|
||||
|
||||
double.deactivate
|
||||
expect(instance.a_method).to eq 42
|
||||
end
|
||||
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]
|
||||
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
|
||||
klass = create_class(a_method: nil)
|
||||
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)
|
||||
|
||||
klass.__send__(:define_method, :a_method) do |*args, &block|
|
||||
actual_args = expected_args
|
||||
actual_block = expected_block
|
||||
method_called = true
|
||||
end
|
||||
|
||||
double.activate
|
||||
double.call_original_method(instance, expected_args, expected_block)
|
||||
|
||||
expect(expected_args).to eq actual_args
|
||||
expect(expected_block).to eq actual_block
|
||||
expect(method_called).to eq true
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
def create_class(methods = {})
|
||||
Class.new.tap do |klass|
|
||||
methods.each do |name, value|
|
||||
klass.__send__(:define_method, name) { |*args| value }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
77
spec/shoulda/matchers/doublespeak/object_double_spec.rb
Normal file
77
spec/shoulda/matchers/doublespeak/object_double_spec.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe ObjectDouble do
|
||||
it 'responds to any method' do
|
||||
double = described_class.new
|
||||
|
||||
expect(double.respond_to?(:foo)).to be_true
|
||||
expect(double.respond_to?(:bar)).to be_true
|
||||
expect(double.respond_to?(:baz)).to be_true
|
||||
end
|
||||
|
||||
it 'returns nil from any method call' do
|
||||
double = described_class.new
|
||||
|
||||
expect(double.foo).to be_nil
|
||||
expect(double.bar).to be_nil
|
||||
expect(double.baz).to be_nil
|
||||
end
|
||||
|
||||
it 'records every method call' do
|
||||
double = described_class.new
|
||||
|
||||
block = -> { :some_return_value }
|
||||
double.foo
|
||||
double.bar(42)
|
||||
double.baz(:zing, :zang, &block)
|
||||
|
||||
expect(double.calls.size).to eq 3
|
||||
double.calls[0].tap do |call|
|
||||
expect(call.args).to eq []
|
||||
expect(call.block).to eq nil
|
||||
end
|
||||
double.calls[1].tap do |call|
|
||||
expect(call.args).to eq [42]
|
||||
expect(call.block).to eq nil
|
||||
end
|
||||
double.calls[2].tap do |call|
|
||||
expect(call.args).to eq [:zing, :zang]
|
||||
expect(call.block).to eq block
|
||||
end
|
||||
end
|
||||
|
||||
describe '#calls_to' do
|
||||
it 'returns all of the invocations of the given method and their arguments/block' do
|
||||
double = described_class.new
|
||||
|
||||
block = -> { :some_return_value }
|
||||
double.foo
|
||||
double.foo(42)
|
||||
double.foo(:zing, :zang, &block)
|
||||
double.some_other_method(:doesnt_matter)
|
||||
|
||||
calls = double.calls_to(:foo)
|
||||
|
||||
expect(calls.size).to eq 3
|
||||
calls[0].tap do |call|
|
||||
expect(call.args).to eq []
|
||||
expect(call.block).to eq nil
|
||||
end
|
||||
calls[1].tap do |call|
|
||||
expect(call.args).to eq [42]
|
||||
expect(call.block).to eq nil
|
||||
end
|
||||
calls[2].tap do |call|
|
||||
expect(call.args).to eq [:zing, :zang]
|
||||
expect(call.block).to eq block
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns an empty array if the given method was never called' do
|
||||
double = described_class.new
|
||||
expect(double.calls_to(:unknown_method)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe ProxyImplementation do
|
||||
describe '#returns' do
|
||||
it 'delegates to its stub_implementation' do
|
||||
stub_implementation = build_stub_implementation
|
||||
stub_implementation.expects(:returns).with(:value)
|
||||
implementation = described_class.new(stub_implementation)
|
||||
implementation.returns(:value)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
it 'delegates to its stub_implementation' do
|
||||
stub_implementation = build_stub_implementation
|
||||
double = build_double
|
||||
stub_implementation.expects(:call).with(double, :object, :args, :block)
|
||||
implementation = described_class.new(stub_implementation)
|
||||
implementation.call(double, :object, :args, :block)
|
||||
end
|
||||
|
||||
it 'calls #call_original_method on the double' do
|
||||
stub_implementation = build_stub_implementation
|
||||
implementation = described_class.new(stub_implementation)
|
||||
double = build_double
|
||||
double.expects(:call_original_method).with(:object, :args, :block)
|
||||
implementation.call(double, :object, :args, :block)
|
||||
end
|
||||
end
|
||||
|
||||
def build_stub_implementation
|
||||
stub(returns: nil, call: nil)
|
||||
end
|
||||
|
||||
def build_double
|
||||
stub(call_original_method: nil)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,88 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe StubImplementation do
|
||||
describe '#call' do
|
||||
it 'calls #record_call on the double' do
|
||||
implementation = described_class.new
|
||||
double = build_double
|
||||
|
||||
double.expects(:record_call).with(:args, :block)
|
||||
|
||||
implementation.call(double, :object, :args, :block)
|
||||
end
|
||||
|
||||
context 'if no explicit implementation was set' do
|
||||
it 'returns nil' do
|
||||
implementation = described_class.new
|
||||
double = build_double
|
||||
|
||||
return_value = implementation.call(double, :object, :args, :block)
|
||||
|
||||
expect(return_value).to eq nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the implementation was set as a value' do
|
||||
it 'returns the set return value' do
|
||||
implementation = described_class.new
|
||||
implementation.returns(42)
|
||||
double = build_double
|
||||
|
||||
return_value = implementation.call(double, :object, :args, :block)
|
||||
|
||||
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
|
||||
double = build_double
|
||||
expected_object, expected_args, expected_block = :object, :args, :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
|
||||
end
|
||||
|
||||
implementation.call(
|
||||
double,
|
||||
expected_object,
|
||||
expected_args,
|
||||
expected_block
|
||||
)
|
||||
|
||||
expect(actual_object).to eq expected_object
|
||||
expect(actual_args).to eq expected_args
|
||||
expect(actual_block).to eq expected_block
|
||||
end
|
||||
|
||||
it 'returns the return value of the block' do
|
||||
implementation = described_class.new
|
||||
implementation.returns { 42 }
|
||||
double = build_double
|
||||
|
||||
return_value = implementation.call(double, :object, :args, :block)
|
||||
|
||||
expect(return_value).to eq 42
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the implementation was set as both a value and a block' do
|
||||
it 'prefers the block over the value' do
|
||||
implementation = described_class.new
|
||||
implementation.returns(:something_else) { 42 }
|
||||
double = build_double
|
||||
|
||||
return_value = implementation.call(double, :object, :args, :block)
|
||||
|
||||
expect(return_value).to eq 42
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_double
|
||||
stub(record_call: nil)
|
||||
end
|
||||
end
|
||||
end
|
88
spec/shoulda/matchers/doublespeak/world_spec.rb
Normal file
88
spec/shoulda/matchers/doublespeak/world_spec.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers::Doublespeak
|
||||
describe World do
|
||||
describe '#register_double_collection' do
|
||||
it 'calls DoubleCollection.new with the given class' do
|
||||
DoubleCollection.expects(:new).with(:klass)
|
||||
world = described_class.new
|
||||
world.register_double_collection(:klass)
|
||||
end
|
||||
|
||||
it 'returns the newly created DoubleCollection' do
|
||||
double_collection = Object.new
|
||||
DoubleCollection.stubs(:new).with(:klass).returns(double_collection)
|
||||
world = described_class.new
|
||||
expect(world.register_double_collection(:klass)).to be double_collection
|
||||
end
|
||||
end
|
||||
|
||||
describe '#with_doubles_activated' do
|
||||
it 'installs all doubles, yields the block, then uninstalls them all' do
|
||||
block_called = false
|
||||
|
||||
double_collections = Array.new(3) do
|
||||
stub.tap do |double_collection|
|
||||
sequence = sequence('with_doubles_activated')
|
||||
double_collection.expects(:activate).in_sequence(sequence)
|
||||
double_collection.expects(:deactivate).in_sequence(sequence)
|
||||
end
|
||||
end
|
||||
|
||||
world = described_class.new
|
||||
|
||||
DoubleCollection.stubs(:new).
|
||||
with(:klass1).
|
||||
returns(double_collections[0])
|
||||
DoubleCollection.stubs(:new).
|
||||
with(:klass2).
|
||||
returns(double_collections[1])
|
||||
DoubleCollection.stubs(:new).
|
||||
with(:klass3).
|
||||
returns(double_collections[2])
|
||||
world.register_double_collection(:klass1)
|
||||
world.register_double_collection(:klass2)
|
||||
world.register_double_collection(:klass3)
|
||||
|
||||
world.with_doubles_activated { block_called = true }
|
||||
|
||||
expect(block_called).to eq true
|
||||
end
|
||||
|
||||
it 'still makes sure to uninstall all doubles even if the block raises an error' do
|
||||
double_collection = stub()
|
||||
double_collection.stubs(:activate)
|
||||
double_collection.expects(:deactivate)
|
||||
|
||||
world = described_class.new
|
||||
|
||||
DoubleCollection.stubs(:new).returns(double_collection)
|
||||
world.register_double_collection(:klass)
|
||||
|
||||
begin
|
||||
world.with_doubles_activated { raise 'error' }
|
||||
rescue RuntimeError
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not allow multiple DoubleCollections to be registered that represent the same class' do
|
||||
double_collections = [stub, stub]
|
||||
sequence = sequence('with_doubles_activated')
|
||||
double_collections[0].expects(:activate).never
|
||||
double_collections[0].expects(:deactivate).never
|
||||
double_collections[1].expects(:activate).in_sequence(sequence)
|
||||
double_collections[1].expects(:deactivate).in_sequence(sequence)
|
||||
|
||||
world = described_class.new
|
||||
|
||||
DoubleCollection.stubs(:new).
|
||||
returns(double_collections[0]).then.
|
||||
returns(double_collections[1])
|
||||
world.register_double_collection(:klass1)
|
||||
world.register_double_collection(:klass1)
|
||||
|
||||
world.with_doubles_activated { }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
spec/shoulda/matchers/doublespeak_spec.rb
Normal file
19
spec/shoulda/matchers/doublespeak_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Shoulda::Matchers
|
||||
describe Doublespeak do
|
||||
describe '.register_double_collection' do
|
||||
it 'delegates to its world' do
|
||||
Doublespeak.world.expects(:register_double_collection).with(:klass)
|
||||
described_class.register_double_collection(:klass)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_doubles_activated' do
|
||||
it 'delegates to its world' do
|
||||
Doublespeak.world.expects(:with_doubles_activated)
|
||||
described_class.with_doubles_activated
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue