diff --git a/README.md b/README.md
index efb60b8a..3d623a1a 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/lib/shoulda/matchers/independent.rb b/lib/shoulda/matchers/independent.rb
index e8119a10..4a72f929 100644
--- a/lib/shoulda/matchers/independent.rb
+++ b/lib/shoulda/matchers/independent.rb
@@ -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
diff --git a/lib/shoulda/matchers/independent/delegate_matcher.rb b/lib/shoulda/matchers/independent/delegate_matcher.rb
index bcf0caa5..8532c545 100644
--- a/lib/shoulda/matchers/independent/delegate_matcher.rb
+++ b/lib/shoulda/matchers/independent/delegate_matcher.rb
@@ -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:
- # * :as - tests that the object being delegated to is called with a certain method
- # (defaults to same name as delegating method)
- # * :with_arguments - tests that the method on the object being delegated to is
- # called with certain arguments
+ # * :as - tests that the object being delegated to is called
+ # with a certain method (defaults to same name as delegating method)
+ # * :with_arguments - 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
diff --git a/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb b/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb
new file mode 100644
index 00000000..cafbe740
--- /dev/null
+++ b/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb
@@ -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
diff --git a/spec/shoulda/matchers/independent/delegate_matcher/stubbed_target_spec.rb b/spec/shoulda/matchers/independent/delegate_matcher/stubbed_target_spec.rb
new file mode 100644
index 00000000..b0d02e07
--- /dev/null
+++ b/spec/shoulda/matchers/independent/delegate_matcher/stubbed_target_spec.rb
@@ -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
diff --git a/spec/shoulda/matchers/independent/delegate_matcher_spec.rb b/spec/shoulda/matchers/independent/delegate_matcher_spec.rb
index 47fb427c..bc31b124 100644
--- a/spec/shoulda/matchers/independent/delegate_matcher_spec.rb
+++ b/spec/shoulda/matchers/independent/delegate_matcher_spec.rb
@@ -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