mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Restore delegate_method matcher
Matcher was removed in commit 77dc477c due to issues described in https://github.com/thoughtbot/shoulda-matchers/issues/252 We've moved away from using Bourne and Mocha's `HaveReceived` matcher and implemented a `StubbedTarget` class for spying on methods to be delegated. We also removed the stub call to be handled via `define_method` for framework-independent support. Other changes include: * Use 1.9 hash syntax in `delegate_method` * Don't explicity test failure messages
This commit is contained in:
parent
f7e4c7d6cb
commit
516fa75f6e
6 changed files with 201 additions and 113 deletions
|
@ -1324,6 +1324,11 @@ Matchers to test non-Rails-dependent code:
|
|||
class Human < ActiveRecord::Base
|
||||
has_one :robot
|
||||
delegate :work, to: :robot
|
||||
|
||||
# alternatively, if you are not using Rails
|
||||
def work
|
||||
robot.work
|
||||
end
|
||||
end
|
||||
|
||||
# RSpec
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
require 'shoulda/matchers/independent/delegate_matcher'
|
||||
require 'shoulda/matchers/independent/delegate_matcher/stubbed_target'
|
||||
|
||||
module Shoulda
|
||||
module Matchers
|
||||
# = Matchers for non-Rails-dependent code.
|
||||
# Matchers for non-Rails-dependent code.
|
||||
module Independent
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
require 'active_support/deprecation'
|
||||
|
||||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module Independent # :nodoc:
|
||||
|
@ -10,13 +8,14 @@ module Shoulda # :nodoc:
|
|||
# it { should delegate_method(:deliver_mail).to(:mailman) }
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:as</tt> - tests that the object being delegated to is called with a certain method
|
||||
# (defaults to same name as delegating method)
|
||||
# * <tt>:with_arguments</tt> - tests that the method on the object being delegated to is
|
||||
# called with certain arguments
|
||||
# * <tt>:as</tt> - tests that the object being delegated to is called
|
||||
# with a certain method (defaults to same name as delegating method)
|
||||
# * <tt>:with_arguments</tt> - tests that the method on the object being
|
||||
# delegated to is called with certain arguments
|
||||
#
|
||||
# Examples:
|
||||
# it { should delegate_method(:deliver_mail).to(:mailman).as(:deliver_with_haste)
|
||||
# it { should delegate_method(:deliver_mail).to(:mailman).
|
||||
# as(:deliver_with_haste) }
|
||||
# it { should delegate_method(:deliver_mail).to(:mailman).
|
||||
# with_arguments('221B Baker St.', :hastily => true) }
|
||||
#
|
||||
|
@ -27,28 +26,26 @@ module Shoulda # :nodoc:
|
|||
class DelegateMatcher
|
||||
def initialize(delegating_method)
|
||||
@delegating_method = delegating_method
|
||||
@delegated_arguments = []
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
def matches?(_subject)
|
||||
@subject = _subject
|
||||
ensure_target_method_is_present!
|
||||
stub_target
|
||||
|
||||
begin
|
||||
extend Mocha::API
|
||||
|
||||
stubbed_object = stub(method_on_target)
|
||||
subject.stubs(@target_method).returns(stubbed_object)
|
||||
subject.send(@delegating_method)
|
||||
|
||||
matcher = Mocha::API::HaveReceived.new(method_on_target).with(*@delegated_arguments)
|
||||
matcher.matches?(stubbed_object)
|
||||
rescue NoMethodError, MiniTest::Assertion
|
||||
subject.send(delegating_method, *delegated_arguments)
|
||||
target_has_received_delegated_method? && target_has_received_arguments?
|
||||
rescue NoMethodError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def description
|
||||
add_clarifications_to("delegate method ##{@delegating_method} to :#{@target_method}")
|
||||
add_clarifications_to(
|
||||
"delegate method ##{delegating_method} to :#{target_method}"
|
||||
)
|
||||
end
|
||||
|
||||
def does_not_match?(subject)
|
||||
|
@ -70,47 +67,74 @@ module Shoulda # :nodoc:
|
|||
self
|
||||
end
|
||||
|
||||
def failure_message_for_should
|
||||
def failure_message
|
||||
base = "Expected #{delegating_method_name} to delegate to #{target_method_name}"
|
||||
add_clarifications_to(base)
|
||||
end
|
||||
alias failure_message_for_should failure_message
|
||||
|
||||
private
|
||||
|
||||
attr_reader :delegated_arguments, :delegating_method, :method, :subject,
|
||||
:target_method, :method_on_target
|
||||
|
||||
def add_clarifications_to(message)
|
||||
if @delegated_arguments.present?
|
||||
message << " with arguments: #{@delegated_arguments.inspect}"
|
||||
if delegated_arguments.present?
|
||||
message << " with arguments: #{delegated_arguments.inspect}"
|
||||
end
|
||||
|
||||
if @method_on_target.present?
|
||||
message << " as ##{@method_on_target}"
|
||||
if method_on_target.present?
|
||||
message << " as ##{method_on_target}"
|
||||
end
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def delegating_method_name
|
||||
method_name_with_class(@delegating_method)
|
||||
method_name_with_class(delegating_method)
|
||||
end
|
||||
|
||||
def target_method_name
|
||||
method_name_with_class(@target_method)
|
||||
method_name_with_class(target_method)
|
||||
end
|
||||
|
||||
def method_name_with_class(method)
|
||||
if Class === @subject
|
||||
@subject.name + '.' + method.to_s
|
||||
if Class === subject
|
||||
subject.name + '.' + method.to_s
|
||||
else
|
||||
@subject.class.name + '#' + method.to_s
|
||||
subject.class.name + '#' + method.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def method_on_target
|
||||
@method_on_target || @delegating_method
|
||||
def target_has_received_delegated_method?
|
||||
stubbed_target.has_received_method?
|
||||
end
|
||||
|
||||
def target_has_received_arguments?
|
||||
stubbed_target.has_received_arguments?(*delegated_arguments)
|
||||
end
|
||||
|
||||
def stubbed_method
|
||||
method_on_target || delegating_method
|
||||
end
|
||||
|
||||
def stub_target
|
||||
local_stubbed_target = stubbed_target
|
||||
local_target_method = target_method
|
||||
|
||||
subject.instance_eval do
|
||||
define_singleton_method local_target_method do
|
||||
local_stubbed_target
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stubbed_target
|
||||
@stubbed_target ||= StubbedTarget.new(stubbed_method)
|
||||
end
|
||||
|
||||
def ensure_target_method_is_present!
|
||||
if @target_method.blank?
|
||||
if target_method.blank?
|
||||
raise TargetNotDefinedError
|
||||
end
|
||||
end
|
||||
|
@ -118,7 +142,8 @@ module Shoulda # :nodoc:
|
|||
|
||||
class DelegateMatcher::TargetNotDefinedError < StandardError
|
||||
def message
|
||||
'Delegation needs a target. Use the #to method to define one, e.g. `post_office.should delegate(:deliver_mail).to(:mailman)`'
|
||||
'Delegation needs a target. Use the #to method to define one, e.g.
|
||||
`post_office.should delegate(:deliver_mail).to(:mailman)`'.squish
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module Independent # :nodoc:
|
||||
class DelegateMatcher::StubbedTarget # :nodoc:
|
||||
def initialize(method)
|
||||
@received_method = false
|
||||
@received_arguments = []
|
||||
stub_method(method)
|
||||
end
|
||||
|
||||
def has_received_method?
|
||||
received_method
|
||||
end
|
||||
|
||||
def has_received_arguments?(*args)
|
||||
args == received_arguments
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stub_method(method)
|
||||
class_eval do
|
||||
define_method method do |*args|
|
||||
@received_method = true
|
||||
@received_arguments = args
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :received_method, :received_arguments
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::Independent::DelegateMatcher::StubbedTarget do
|
||||
subject(:target) { described_class.new(:stubbed_method) }
|
||||
|
||||
describe '#has_received_method?' do
|
||||
it 'returns true when the method has been called on the target' do
|
||||
target.stubbed_method
|
||||
|
||||
expect(target).to have_received_method
|
||||
end
|
||||
|
||||
it 'returns false when the method has not been called on the target' do
|
||||
expect(target).not_to have_received_method
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_received_arguments?' do
|
||||
context 'method is called with specified arguments' do
|
||||
it 'returns true' do
|
||||
target.stubbed_method(:arg1, :arg2)
|
||||
|
||||
expect(target).to have_received_arguments(:arg1, :arg2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'method is not called with specified arguments' do
|
||||
it 'returns false' do
|
||||
target.stubbed_method
|
||||
|
||||
expect(target).not_to have_received_arguments(:arg1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'method is called with arguments in incorrect order' do
|
||||
it 'returns false' do
|
||||
target.stubbed_method(:arg2, :arg1)
|
||||
|
||||
expect(target).not_to have_received_arguments(:arg1, :arg2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,59 +1,46 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::Independent::DelegateMatcher do
|
||||
context '#description' do
|
||||
describe '#description' do
|
||||
context 'by default' do
|
||||
it 'states that it should delegate method to the right object' do
|
||||
matcher = delegate_method(:method_name).to(:target)
|
||||
|
||||
matcher.description.should == 'delegate method #method_name to :target'
|
||||
expect(matcher.description)
|
||||
.to eq 'delegate method #method_name to :target'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with #as chain' do
|
||||
it 'states that it should delegate method to the right object and method' do
|
||||
matcher = delegate_method(:method_name).to(:target).as(:alternate)
|
||||
message = 'delegate method #method_name to :target as #alternate'
|
||||
|
||||
matcher.description.should == 'delegate method #method_name to :target as #alternate'
|
||||
expect(matcher.description).to eq message
|
||||
end
|
||||
end
|
||||
|
||||
context 'with #with_argument chain' do
|
||||
it 'states that it should delegate method to the right object with right argument' do
|
||||
matcher = delegate_method(:method_name).to(:target).with_arguments(:foo, :bar => [1, 2])
|
||||
matcher = delegate_method(:method_name).to(:target)
|
||||
.with_arguments(:foo, bar: [1, 2])
|
||||
message = 'delegate method #method_name to :target with arguments: [:foo, {:bar=>[1, 2]}]'
|
||||
|
||||
matcher.description.should ==
|
||||
'delegate method #method_name to :target with arguments: [:foo, {:bar=>[1, 2]}]'
|
||||
expect(matcher.description).to eq message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'supports chaining on #to' do
|
||||
matcher = delegate_method(:method)
|
||||
|
||||
matcher.to(:another_method).should == matcher
|
||||
end
|
||||
|
||||
it 'supports chaining on #with_arguments' do
|
||||
matcher = delegate_method(:method)
|
||||
|
||||
matcher.with_arguments(1, 2, 3).should == matcher
|
||||
end
|
||||
|
||||
it 'supports chaining on #as' do
|
||||
matcher = delegate_method(:method)
|
||||
|
||||
matcher.as(:some_other_method).should == matcher
|
||||
end
|
||||
|
||||
it 'raises an error if no delegation target is defined' do
|
||||
expect { Object.new.should delegate_method(:name) }.
|
||||
to raise_exception described_class::TargetNotDefinedError
|
||||
expect {
|
||||
delegate_method(:name).matches?(Object.new)
|
||||
}.to raise_exception described_class::TargetNotDefinedError
|
||||
end
|
||||
|
||||
it 'raises an error if called with #should_not' do
|
||||
expect { Object.new.should_not delegate_method(:name).to(:anyone) }.
|
||||
to raise_exception described_class::InvalidDelegateMatcher
|
||||
expect {
|
||||
delegate_method(:name).to(:anyone).does_not_match?(Object.new)
|
||||
}.to raise_exception described_class::InvalidDelegateMatcher
|
||||
end
|
||||
|
||||
context 'given a method that does not delegate' do
|
||||
|
@ -69,32 +56,31 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
|
|||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman)
|
||||
|
||||
matcher.matches?(post_office).should be_false
|
||||
expect(matcher.matches?(post_office)).to be false
|
||||
end
|
||||
|
||||
it 'has a failure message that indicates which method should have been delegated' do
|
||||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman)
|
||||
|
||||
matcher.matches?(post_office)
|
||||
|
||||
message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman'
|
||||
matcher.failure_message_for_should.should == message
|
||||
|
||||
expect {
|
||||
expect(post_office).to delegate_method(:deliver_mail).to(:mailman)
|
||||
}.to fail_with_message(message)
|
||||
end
|
||||
|
||||
it 'uses the proper syntax for class methods in errors' do
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman)
|
||||
|
||||
matcher.matches?(PostOffice)
|
||||
|
||||
message = 'Expected PostOffice.deliver_mail to delegate to PostOffice.mailman'
|
||||
matcher.failure_message_for_should.should == message
|
||||
|
||||
expect {
|
||||
expect(PostOffice).to delegate_method(:deliver_mail).to(:mailman)
|
||||
}.to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a method that delegates properly' do
|
||||
it 'accepts' do
|
||||
define_class(:mailman)
|
||||
|
||||
define_class(:post_office) do
|
||||
def deliver_mail
|
||||
mailman.deliver_mail
|
||||
|
@ -103,16 +89,23 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
|
|||
def mailman
|
||||
Mailman.new
|
||||
end
|
||||
end.new.should delegate_method(:deliver_mail).to(:mailman)
|
||||
end
|
||||
|
||||
post_office = PostOffice.new
|
||||
|
||||
expect(post_office).to delegate_method(:deliver_mail).to(:mailman)
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a method that delegates properly with certain arguments' do
|
||||
context 'given a method that delegates properly with arguments' do
|
||||
let(:post_office) { PostOffice.new }
|
||||
|
||||
before do
|
||||
define_class(:mailman)
|
||||
|
||||
define_class(:post_office) do
|
||||
def deliver_mail
|
||||
mailman.deliver_mail('221B Baker St.', :hastily => true)
|
||||
def deliver_mail(*args)
|
||||
mailman.deliver_mail('221B Baker St.', hastily: true)
|
||||
end
|
||||
|
||||
def mailman
|
||||
|
@ -123,34 +116,36 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
|
|||
|
||||
context 'when given the correct arguments' do
|
||||
it 'accepts' do
|
||||
PostOffice.new.should delegate_method(:deliver_mail).
|
||||
to(:mailman).with_arguments('221B Baker St.', :hastily => true)
|
||||
expect(post_office).to delegate_method(:deliver_mail)
|
||||
.to(:mailman).with_arguments('221B Baker St.', hastily: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not given the correct arguments' do
|
||||
it 'rejects' do
|
||||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman).
|
||||
with_arguments('123 Nowhere Ln.')
|
||||
matcher.matches?(post_office).should be_false
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman)
|
||||
.with_arguments('123 Nowhere Ln.')
|
||||
|
||||
expect(matcher.matches?(post_office)).to be_false
|
||||
end
|
||||
|
||||
it 'has a failure message that indicates which arguments were expected' do
|
||||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman).with_arguments('123 Nowhere Ln.')
|
||||
|
||||
matcher.matches?(post_office)
|
||||
|
||||
message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman with arguments: ["123 Nowhere Ln."]'
|
||||
matcher.failure_message_for_should.should == message
|
||||
|
||||
expect {
|
||||
expect(post_office).to delegate_method(:deliver_mail)
|
||||
.to(:mailman).with_arguments('123 Nowhere Ln.')
|
||||
}.to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a method that delegates properly to a method of a different name' do
|
||||
let(:post_office) { PostOffice.new }
|
||||
|
||||
before do
|
||||
define_class(:mailman)
|
||||
|
||||
define_class(:post_office) do
|
||||
def deliver_mail
|
||||
mailman.deliver_mail_and_avoid_dogs
|
||||
|
@ -164,41 +159,26 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
|
|||
|
||||
context 'when given the correct method name' do
|
||||
it 'accepts' do
|
||||
PostOffice.new.
|
||||
should delegate_method(:deliver_mail).to(:mailman).as(:deliver_mail_and_avoid_dogs)
|
||||
expect(post_office).to delegate_method(:deliver_mail)
|
||||
.to(:mailman).as(:deliver_mail_and_avoid_dogs)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given an incorrect method name' do
|
||||
it 'rejects' do
|
||||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman).as(:watch_tv)
|
||||
matcher.matches?(post_office).should be_false
|
||||
|
||||
expect(matcher.matches?(post_office)).to be_false
|
||||
end
|
||||
|
||||
it 'has a failure message that indicates which method was expected' do
|
||||
post_office = PostOffice.new
|
||||
matcher = delegate_method(:deliver_mail).to(:mailman).as(:watch_tv)
|
||||
|
||||
matcher.matches?(post_office)
|
||||
|
||||
message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman as #watch_tv'
|
||||
matcher.failure_message_for_should.should == message
|
||||
|
||||
expect {
|
||||
expect(post_office).to delegate_method(:deliver_mail)
|
||||
.to(:mailman).as(:watch_tv)
|
||||
}.to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Shoulda::Matchers::Independent::DelegateMatcher::TargetNotDefinedError do
|
||||
it 'has a useful message' do
|
||||
error = Shoulda::Matchers::Independent::DelegateMatcher::TargetNotDefinedError.new
|
||||
error.message.should include 'Delegation needs a target'
|
||||
end
|
||||
end
|
||||
|
||||
describe Shoulda::Matchers::Independent::DelegateMatcher::InvalidDelegateMatcher do
|
||||
it 'has a useful message' do
|
||||
error = Shoulda::Matchers::Independent::DelegateMatcher::InvalidDelegateMatcher.new
|
||||
error.message.should include 'does not support #should_not'
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue