Adding support to allow_nil option for delegate_method matcher

This commit is contained in:
Alejandro Gutiérrez 2015-10-07 21:48:10 -05:00 committed by Elliot Winkler
parent ba5b663235
commit d49cfcae1b
3 changed files with 234 additions and 42 deletions

View File

@ -162,10 +162,11 @@ module Shoulda
@delegate_method = @delegating_method
@delegate_object = Doublespeak::ObjectDouble.new
@delegated_arguments = []
@delegate_object_reader_method = nil
@context = nil
@subject = nil
@subject_double_collection = nil
@delegate_object_reader_method = nil
@delegated_arguments = []
@expects_to_allow_nil_delegate_object = false
end
def in_context(context)
@ -180,11 +181,13 @@ module Shoulda
subject_has_delegating_method? &&
subject_has_delegate_object_reader_method? &&
subject_delegates_to_delegate_object_correctly?
subject_delegates_to_delegate_object_correctly? &&
subject_handles_nil_delegate_object?
end
def description
string = "delegate #{formatted_delegating_method_name} to " +
string =
"delegate #{formatted_delegating_method_name} to the " +
"#{formatted_delegate_object_reader_method_name} object"
if delegated_arguments.any?
@ -195,6 +198,12 @@ module Shoulda
string << " as #{formatted_delegate_method}"
end
if expects_to_allow_nil_delegate_object?
string << ", allowing "
string << formatted_delegate_object_reader_method_name
string << " to return nil"
end
string
end
@ -220,6 +229,11 @@ module Shoulda
self
end
def allow_nil
@expects_to_allow_nil_delegate_object = true
self
end
def build_delegating_method_prefix(prefix)
case prefix
when true, nil then delegate_object_reader_method
@ -228,14 +242,29 @@ module Shoulda
end
def failure_message
"Expected #{class_under_test} to #{description}\n" +
"Method calls sent to " +
"#{formatted_delegate_object_reader_method_name(include_module: true)}:" +
formatted_calls_on_delegate_object
message = "Expected #{class_under_test} to #{description}.\n\n"
if failed_to_allow_nil_delegate_object?
message << formatted_delegating_method_name(include_module: true)
message << ' did delegate to '
message << formatted_delegate_object_reader_method_name
message << ' when it was non-nil, but it failed to account '
message << 'for when '
message << formatted_delegate_object_reader_method_name
message << ' *was* nil.'
else
message << 'Method calls sent to '
message << formatted_delegate_object_reader_method_name(
include_module: true,
)
message << ": #{formatted_calls_on_delegate_object}"
end
Shoulda::Matchers.word_wrap(message)
end
def failure_message_when_negated
"Expected #{class_under_test} not to #{description}, but it did"
"Expected #{class_under_test} not to #{description}, but it did."
end
protected
@ -246,7 +275,6 @@ module Shoulda
:delegating_method,
:method,
:delegate_method,
:subject_double_collection,
:delegate_object,
:delegate_object_reader_method
@ -270,6 +298,10 @@ module Shoulda
end
end
def expects_to_allow_nil_delegate_object?
@expects_to_allow_nil_delegate_object
end
def formatted_delegate_method(options = {})
formatted_method_name_for(delegate_method, options)
end
@ -329,11 +361,7 @@ module Shoulda
end
def subject_delegates_to_delegate_object_correctly?
register_subject_double_collection
Doublespeak.with_doubles_activated do
subject.public_send(delegating_method, *delegated_arguments)
end
call_delegating_method_with_delegate_method_returning(delegate_object)
if delegated_arguments.any?
delegate_object_received_call_with_delegated_arguments?
@ -342,13 +370,44 @@ module Shoulda
end
end
def register_subject_double_collection
def subject_handles_nil_delegate_object?
@subject_handled_nil_delegate_object =
if expects_to_allow_nil_delegate_object?
begin
call_delegating_method_with_delegate_method_returning(nil)
true
rescue Module::DelegationError
false
rescue NoMethodError => error
if error.message =~ /undefined method `#{delegate_method}' for nil:NilClass/
false
else
raise error
end
end
else
true
end
end
def failed_to_allow_nil_delegate_object?
expects_to_allow_nil_delegate_object? &&
!@subject_handled_nil_delegate_object
end
def call_delegating_method_with_delegate_method_returning(value)
register_subject_double_collection_to(value)
Doublespeak.with_doubles_activated do
subject.public_send(delegating_method, *delegated_arguments)
end
end
def register_subject_double_collection_to(returned_value)
double_collection =
Doublespeak.double_collection_for(subject.singleton_class)
double_collection.register_stub(delegate_object_reader_method).
to_return(delegate_object)
@subject_double_collection = double_collection
to_return(returned_value)
end
def calls_to_delegate_method
@ -363,7 +422,7 @@ module Shoulda
string = ""
if calls_on_delegate_object.any?
string << "\n"
string << "\n\n"
calls_on_delegate_object.each_with_index do |call, i|
name = call.method_name
args = call.args.map { |arg| arg.inspect }.join(', ')

View File

@ -56,7 +56,7 @@ describe 'shoulda-matchers has independent matchers, specifically delegate_metho
expect(result).to indicate_number_of_tests_was_run(1)
expect(result).to have_output(
'Courier should delegate #deliver to #post_office object'
'Courier should delegate #deliver to the #post_office object'
)
end
@ -119,7 +119,7 @@ describe 'shoulda-matchers has independent matchers, specifically delegate_metho
expect(result).to indicate_number_of_tests_was_run(1)
expect(result).to have_output(
/Courier\s+should delegate #deliver to #post_office object/
/Courier\s+should delegate #deliver to the #post_office object/
)
end
end

View File

@ -8,7 +8,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'without any qualifiers' do
it 'states that it should delegate method to the right object' do
matcher = delegate_method(:method_name).to(:delegate)
message = 'delegate #method_name to #delegate object'
message = 'delegate #method_name to the #delegate object'
expect(matcher.description).to eq message
end
@ -17,7 +17,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'qualified with #as' do
it 'states that it should delegate method to the right object and method' do
matcher = delegate_method(:method_name).to(:delegate).as(:alternate)
message = 'delegate #method_name to #delegate object as #alternate'
message = 'delegate #method_name to the #delegate object as #alternate'
expect(matcher.description).to eq message
end
@ -27,7 +27,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
it 'states that it should delegate method to the right object with right argument' do
matcher = delegate_method(:method_name).to(:delegate).
with_arguments(:foo, bar: [1, 2])
message = 'delegate #method_name to #delegate object passing arguments [:foo, {:bar=>[1, 2]}]'
message = 'delegate #method_name to the #delegate object passing arguments [:foo, {:bar=>[1, 2]}]'
expect(matcher.description).to eq message
end
@ -53,7 +53,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
matcher = delegate_method(:hello).to(:country).with_prefix
expect(matcher.description).
to eq('delegate #country_hello to #country object as #hello')
to eq('delegate #country_hello to the #country object as #hello')
end
end
end
@ -77,7 +77,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
matcher = delegate_method(:hello).to(:country).with_prefix(true)
expect(matcher.description).
to eq('delegate #country_hello to #country object as #hello')
to eq('delegate #country_hello to the #country object as #hello')
end
end
end
@ -98,7 +98,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
matcher = delegate_method(:hello).to(:country).with_prefix('county')
expect(matcher.description).
to eq('delegate #county_hello to #country object as #hello')
to eq('delegate #county_hello to the #country object as #hello')
end
end
end
@ -112,14 +112,14 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
matcher = delegate_method(:method_name).to(:delegate)
expect(matcher.description).
to eq 'delegate .method_name to .delegate object'
to eq 'delegate .method_name to the .delegate object'
end
end
context 'qualified with #as' do
it 'states that it should delegate method to the right object and method' do
matcher = delegate_method(:method_name).to(:delegate).as(:alternate)
message = 'delegate .method_name to .delegate object as .alternate'
message = 'delegate .method_name to the .delegate object as .alternate'
expect(matcher.description).to eq message
end
@ -129,7 +129,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
it 'states that it should delegate method to the right object with right argument' do
matcher = delegate_method(:method_name).to(:delegate).
with_arguments(:foo, bar: [1, 2])
message = 'delegate .method_name to .delegate object passing arguments [:foo, {:bar=>[1, 2]}]'
message = 'delegate .method_name to the .delegate object passing arguments [:foo, {:bar=>[1, 2]}]'
expect(matcher.description).to eq message
end
@ -178,7 +178,8 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = [
'Expected PostOffice to delegate #deliver_mail to #mailman object',
'Expected PostOffice to delegate #deliver_mail to the #mailman object.',
'',
'Method calls sent to PostOffice#mailman: (none)'
].join("\n")
@ -191,7 +192,8 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'when the subject is a class' do
it 'uses the proper syntax for class methods in errors' do
message = [
'Expected PostOffice to delegate .deliver_mail to .mailman object',
'Expected PostOffice to delegate .deliver_mail to the .mailman object.',
'',
'Method calls sent to PostOffice.mailman: (none)'
].join("\n")
@ -225,7 +227,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'negating the matcher' do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = 'Expected PostOffice not to delegate #deliver_mail to #mailman object, but it did'
message = 'Expected PostOffice not to delegate #deliver_mail to the #mailman object, but it did.'
expect {
expect(post_office).not_to delegate_method(:deliver_mail).to(:mailman)
@ -283,7 +285,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'negating the matcher' do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = 'Expected PostOffice not to delegate #deliver_mail to #mailman object passing arguments ["221B Baker St.", {:hastily=>true}], but it did'
message = 'Expected PostOffice not to delegate #deliver_mail to the #mailman object passing arguments ["221B Baker St.", {:hastily=>true}], but it did.'
expect {
expect(post_office).
@ -299,8 +301,11 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = [
'Expected PostOffice to delegate #deliver_mail to #mailman object passing arguments ["123 Nowhere Ln."]',
'Expected PostOffice to delegate #deliver_mail to the #mailman object',
'passing arguments ["123 Nowhere Ln."].',
'',
'Method calls sent to PostOffice#mailman:',
'',
'1) deliver_mail("221B Baker St.", {:hastily=>true})'
].join("\n")
@ -338,7 +343,7 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
context 'negating the assertion' do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = 'Expected PostOffice not to delegate #deliver_mail to #mailman object as #deliver_mail_and_avoid_dogs, but it did'
message = 'Expected PostOffice not to delegate #deliver_mail to the #mailman object as #deliver_mail_and_avoid_dogs, but it did.'
expect {
expect(post_office).
@ -354,8 +359,11 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
it 'rejects with the correct failure message' do
post_office = PostOffice.new
message = [
'Expected PostOffice to delegate #deliver_mail to #mailman object as #watch_tv',
'Expected PostOffice to delegate #deliver_mail to the #mailman object as',
'#watch_tv.',
'',
'Method calls sent to PostOffice#mailman:',
'',
'1) deliver_mail_and_avoid_dogs()'
].join("\n")
@ -401,7 +409,9 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
end
message = [
'Expected Person to delegate #country_hello to #country object as #hello',
'Expected Person to delegate #country_hello to the #country object as',
'#hello.',
'',
'Method calls sent to Person#country: (none)'
].join("\n")
@ -449,7 +459,9 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
end
message = [
'Expected Person to delegate #country_hello to #country object as #hello',
'Expected Person to delegate #country_hello to the #country object as',
'#hello.',
'',
'Method calls sent to Person#country: (none)'
].join("\n")
@ -499,7 +511,9 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
end
message = [
'Expected Person to delegate #county_hello to #country object as #hello',
'Expected Person to delegate #county_hello to the #country object as',
'#hello.',
'',
'Method calls sent to Person#country: (none)'
].join("\n")
@ -514,4 +528,123 @@ describe Shoulda::Matchers::Independent::DelegateMethodMatcher do
end
end
end
context 'qualified with #allow_nil' do
context 'when using delegate from Rails' do
context 'when delegations were defined with :allow_nil' do
it 'accepts' do
define_class('Person') do
delegate :hello, to: :country, allow_nil: true
def country; end
end
person = Person.new
expect(person).to delegate_method(:hello).to(:country).allow_nil
end
end
context 'when delegations were not defined with :allow_nil' do
it 'rejects with the correct failure message' do
define_class('Person') do
delegate :hello, to: :country
def country; end
end
person = Person.new
message = <<-MESSAGE
Expected Person to delegate #hello to the #country object, allowing
#country to return nil.
Person#hello did delegate to #country when it was non-nil, but it failed
to account for when #country *was* nil.
MESSAGE
expectation = lambda do
expect(person).to delegate_method(:hello).to(:country).allow_nil
end
expect(&expectation).to fail_with_message(message)
end
end
end
context 'when using Forwardable' do
context 'when the delegate object is nil' do
it 'rejects with the correct failure message' do
define_class('Person') do
extend Forwardable
def_delegators :country, :hello
def country; end
end
person = Person.new
message = <<-MESSAGE
Expected Person to delegate #hello to the #country object, allowing
#country to return nil.
Person#hello did delegate to #country when it was non-nil, but it failed
to account for when #country *was* nil.
MESSAGE
expectation = lambda do
expect(person).to delegate_method(:hello).to(:country).allow_nil
end
expect(&expectation).to fail_with_message(message)
end
end
end
context 'when delegating manually' do
context 'when the delegating method accounts for the delegate object being nil' do
it 'accepts' do
define_class('Person') do
def country; end
def hello
return unless country
country.hello
end
end
person = Person.new
expect(person).to delegate_method(:hello).to(:country).allow_nil
end
end
context 'when the delegating method does not account for the delegate object being nil' do
it 'rejects with the correct failure message' do
define_class('Person') do
def country; end
def hello
country.hello
end
end
person = Person.new
message = <<-MESSAGE
Expected Person to delegate #hello to the #country object, allowing
#country to return nil.
Person#hello did delegate to #country when it was non-nil, but it failed
to account for when #country *was* nil.
MESSAGE
expectation = lambda do
expect(person).to delegate_method(:hello).to(:country).allow_nil
end
expect(&expectation).to fail_with_message(message)
end
end
end
end
end