thoughtbot--shoulda-matchers/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb

191 lines
6.2 KiB
Ruby
Raw Normal View History

require 'doublespeak_spec_helper'
module Shoulda::Matchers::Doublespeak
describe DoubleCollection do
describe '#register_stub' do
it 'calls DoubleImplementationRegistry.find correctly' do
2014-11-07 17:22:20 -05:00
allow(DoubleImplementationRegistry).to receive(:find)
double_collection = described_class.new(build_world, :klass)
2014-11-07 17:22:20 -05:00
double_collection.register_stub(:a_method)
2014-11-07 17:22:20 -05:00
expect(DoubleImplementationRegistry).to have_received(:find).with(:stub)
end
it 'calls Double.new correctly' do
world = build_world
2014-11-07 17:22:20 -05:00
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
2014-11-07 17:22:20 -05:00
double_collection.register_stub(:a_method)
2014-11-07 17:22:20 -05:00
expect(Double).
to have_received(:new).
with(world, :klass, :a_method, :implementation)
end
Improve architecture for permit matcher Why: * There were architectural issues with how the permit matcher kept track of params instances on which doubles had been placed. Previously we were starting off by taking the ActionController::Parameters class and stubbing the #permit and #require instance method on it -- in other words, we were stubbing #require for all instances of ActionController::Parameters -- then we would stub #permit on a particular instance of ActionController::Parameters that #require returned. What this means is that if for some reason the #permit stub on an individual instance isn't working properly, then the #permit stub on ActionController::Parameters will respond to the invocation. This is exactly what happened for the issue we recently fixed -- if the stubbing were done a different way we wouldn't have run into that issue. * Also, there's no reason to have both ParametersDoubles and SliceOfParametersDoubles classes around. While it's nice that we have a simpler option to use if we don't need the more complex one, we actually don't need a distinction here, and we can afford one class that does both. To satisfy the above: * When stubbing #permit or #require, always do so on an instance of ActionController::Parameters and not the whole class. This way we know exactly which methods are being doubled and it's easier to debug things in the future. * This means that we now stub ActionController::Parameters.new and then place stubs on the returned instance. * Refactor ParametersDoubles and SliceOfParametersDoubles: combine them into a ParametersDoubleRegistry class, but extract the code that stubs ActionController::Parameters.new into a CompositeParametersDoubleRegistry class. * Since this broke one of the tests, modify DoubleCollection so that a method cannot be doubled more than once -- if the method is already doubled then `register_stub` or `register_proxy` does nothing and returns the original Double.
2015-09-29 20:14:00 -04:00
context 'if a double has already been registered for the method' do
it 'does not call Double.new again' do
world = build_world
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
double_collection.register_stub(:a_method)
double_collection.register_stub(:a_method)
expect(Double).to have_received(:new).once
end
it 'returns the same Double' do
world = build_world
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
double1 = double_collection.register_stub(:a_method)
double2 = double_collection.register_stub(:a_method)
expect(double1).to equal(double2)
end
end
end
describe '#register_proxy' do
it 'calls DoubleImplementationRegistry.find correctly' do
2014-11-07 17:22:20 -05:00
allow(DoubleImplementationRegistry).to receive(:find)
double_collection = described_class.new(build_world, :klass)
2014-11-07 17:22:20 -05:00
double_collection.register_proxy(:a_method)
2014-11-07 17:22:20 -05:00
expect(DoubleImplementationRegistry).
to have_received(:find).
with(:proxy)
end
it 'calls Double.new correctly' do
world = build_world
2014-11-07 17:22:20 -05:00
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
2014-11-07 17:22:20 -05:00
double_collection.register_proxy(:a_method)
2014-11-07 17:22:20 -05:00
expect(Double).
to have_received(:new).
with(world, :klass, :a_method, :implementation)
end
Improve architecture for permit matcher Why: * There were architectural issues with how the permit matcher kept track of params instances on which doubles had been placed. Previously we were starting off by taking the ActionController::Parameters class and stubbing the #permit and #require instance method on it -- in other words, we were stubbing #require for all instances of ActionController::Parameters -- then we would stub #permit on a particular instance of ActionController::Parameters that #require returned. What this means is that if for some reason the #permit stub on an individual instance isn't working properly, then the #permit stub on ActionController::Parameters will respond to the invocation. This is exactly what happened for the issue we recently fixed -- if the stubbing were done a different way we wouldn't have run into that issue. * Also, there's no reason to have both ParametersDoubles and SliceOfParametersDoubles classes around. While it's nice that we have a simpler option to use if we don't need the more complex one, we actually don't need a distinction here, and we can afford one class that does both. To satisfy the above: * When stubbing #permit or #require, always do so on an instance of ActionController::Parameters and not the whole class. This way we know exactly which methods are being doubled and it's easier to debug things in the future. * This means that we now stub ActionController::Parameters.new and then place stubs on the returned instance. * Refactor ParametersDoubles and SliceOfParametersDoubles: combine them into a ParametersDoubleRegistry class, but extract the code that stubs ActionController::Parameters.new into a CompositeParametersDoubleRegistry class. * Since this broke one of the tests, modify DoubleCollection so that a method cannot be doubled more than once -- if the method is already doubled then `register_stub` or `register_proxy` does nothing and returns the original Double.
2015-09-29 20:14:00 -04:00
context 'if a double has already been registered for the method' do
it 'does not call Double.new again' do
world = build_world
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
double_collection.register_proxy(:a_method)
double_collection.register_proxy(:a_method)
expect(Double).to have_received(:new).once
end
it 'returns the same Double' do
world = build_world
allow(DoubleImplementationRegistry).
to receive(:find).
and_return(:implementation)
allow(Double).to receive(:new)
double_collection = described_class.new(world, :klass)
double1 = double_collection.register_proxy(:a_method)
double2 = double_collection.register_proxy(:a_method)
expect(double1).to equal(double2)
end
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(build_world, 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(build_world, 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(build_world, 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(build_world, 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
def build_world
Shoulda::Matchers::Doublespeak::World.new
end
end
end