forked from thoughtbot/shoulda-matchers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c8e3f09
commit 2377fb4
Showing
5 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
require 'shoulda/matchers/independent/delegate_matcher' | ||
|
||
module Shoulda | ||
module Matchers | ||
# = Matchers for plain ol' Ruby objects. | ||
module Independent | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
module Shoulda # :nodoc: | ||
module Matchers | ||
module Independent # :nodoc: | ||
|
||
# Ensure that a given method is delegated properly. | ||
# | ||
# Basic Syntax: | ||
# 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 | ||
# | ||
# Examples: | ||
# 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.", speed: :presently) | ||
# | ||
def delegate_method(delegating_method) | ||
require 'bourne' | ||
DelegateMatcher.new(delegating_method) | ||
rescue LoadError | ||
raise "To use Shoulda's #delegate_method matcher, please add `bourne` to your Gemfile." | ||
end | ||
|
||
class DelegateMatcher | ||
def initialize(delegating_method) | ||
@delegating_method = delegating_method | ||
end | ||
|
||
def matches?(subject) | ||
@subject = subject | ||
ensure_target_method_is_present! | ||
|
||
begin | ||
extend Mocha::API | ||
|
||
stubbed_object = stub(method_on_target) | ||
subject.stubs(@target_method).returns(stubbed_object) | ||
subject.send(@delegating_method) | ||
|
||
stubbed_object.should have_received(method_on_target).with(*@delegated_arguments) | ||
rescue NoMethodError, RSpec::Expectations::ExpectationNotMetError, Mocha::ExpectationError | ||
false | ||
end | ||
end | ||
|
||
def does_not_match?(subject) | ||
raise InvalidDelegateMatcher | ||
end | ||
|
||
def to(target_method) | ||
@target_method = target_method | ||
self | ||
end | ||
|
||
def as(method_on_target) | ||
@method_on_target = method_on_target | ||
self | ||
end | ||
|
||
def with_arguments(*arguments) | ||
@delegated_arguments = arguments | ||
self | ||
end | ||
|
||
def failure_message | ||
base = "Expected #{delegating_method_name} to delegate to #{target_method_name}" | ||
add_clarifications_to(base) | ||
end | ||
|
||
private | ||
|
||
def add_clarifications_to(message) | ||
message.tap do |message| | ||
if @delegated_arguments.present? | ||
message.concat(" with arguments: #{@delegated_arguments}") | ||
end | ||
|
||
if @method_on_target.present? | ||
message.concat(" as :#{@method_on_target}") | ||
end | ||
end | ||
end | ||
|
||
def delegating_method_name | ||
method_name_with_class(@delegating_method) | ||
end | ||
|
||
def target_method_name | ||
method_name_with_class(@target_method) | ||
end | ||
|
||
def method_name_with_class(method) | ||
if Class === @subject | ||
@subject.name + "." + method.to_s | ||
else | ||
@subject.class.name + "#" + method.to_s | ||
end | ||
end | ||
|
||
def method_on_target | ||
@method_on_target || @delegating_method | ||
end | ||
|
||
def ensure_target_method_is_present! | ||
raise TargetNotDefinedError unless @target_method.present? | ||
end | ||
end | ||
|
||
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)`" | ||
end | ||
end | ||
|
||
class DelegateMatcher::InvalidDelegateMatcher < StandardError | ||
def message | ||
"#delegate_to does not support #should_not syntax." | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
require 'spec_helper' | ||
|
||
describe Shoulda::Matchers::Independent::DelegateMatcher do | ||
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 'should raise an error if no delegation target is defined' do | ||
object = Object.new | ||
expect { | ||
object.should delegate_method(:name) | ||
}.to raise_exception Shoulda::Matchers::Independent::DelegateMatcher::TargetNotDefinedError | ||
end | ||
|
||
it 'should raise an error if called with #should_not' do | ||
object = Object.new | ||
expect { | ||
object.should_not delegate_method(:name).to(:anyone) | ||
}.to raise_exception Shoulda::Matchers::Independent::DelegateMatcher::InvalidDelegateMatcher | ||
end | ||
|
||
context 'given a method that does not delegate' do | ||
before do | ||
class PostOffice | ||
def deliver_mail | ||
:delivered | ||
end | ||
end | ||
end | ||
|
||
it 'fails with a useful message' do | ||
begin | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail).to(:mailman) | ||
rescue Exception => e | ||
e.message.should == 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman' | ||
end | ||
end | ||
|
||
it 'uses the proper syntax for class methods in errors' do | ||
begin | ||
PostOffice.should delegate_method(:deliver_mail).to(:mailman) | ||
rescue => e | ||
e.message.should == 'Expected PostOffice.deliver_mail to delegate to PostOffice.mailman' | ||
end | ||
end | ||
end | ||
|
||
context 'given a method that delegates properly' do | ||
before do | ||
class Mailman; end | ||
class PostOffice | ||
def deliver_mail | ||
mailman.deliver_mail | ||
end | ||
|
||
def mailman | ||
Mailman.new | ||
end | ||
end | ||
end | ||
|
||
it 'succeeds' do | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail).to(:mailman) | ||
end | ||
end | ||
|
||
context 'given a method that delegates properly with certain arguments' do | ||
before do | ||
class Mailman; end | ||
class PostOffice | ||
def deliver_mail | ||
mailman.deliver_mail("221B Baker St.", speed: :presently) | ||
end | ||
|
||
def mailman | ||
Mailman.new | ||
end | ||
end | ||
end | ||
|
||
context 'when given the correct arguments' do | ||
it 'succeeds' do | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail) | ||
.to(:mailman) | ||
.with_arguments("221B Baker St.", speed: :presently) | ||
end | ||
end | ||
|
||
context 'when not given the correct arguments' do | ||
it 'fails with a useful message' do | ||
begin | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail) | ||
.to(:mailman) | ||
.with_arguments("123 Nowhere Ln.") | ||
rescue Exception => e | ||
e.message.should == 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman with arguments: ["123 Nowhere Ln."]' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'given a method that delegates properly to a method of a different name' do | ||
before do | ||
class Mailman; end | ||
class PostOffice | ||
def deliver_mail | ||
mailman.deliver_mail_and_avoid_dogs | ||
end | ||
|
||
def mailman | ||
Mailman.new | ||
end | ||
end | ||
end | ||
|
||
context 'when given the correct method name' do | ||
it 'succeeds' do | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail) | ||
.to(:mailman) | ||
.as(:deliver_mail_and_avoid_dogs) | ||
end | ||
end | ||
|
||
context 'when given an incorrect method name' do | ||
it 'fails with a useful message' do | ||
begin | ||
post_office = PostOffice.new | ||
post_office.should delegate_method(:deliver_mail) | ||
.to(:mailman) | ||
.as(:deliver_mail_without_regard_for_safety) | ||
rescue Exception => e | ||
e.message.should == "Expected PostOffice#deliver_mail to delegate to PostOffice#mailman as :deliver_mail_without_regard_for_safety" | ||
end | ||
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 |