mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Fix delegate_method when used with shoulda-context
When specifying a custom subject, a straightforward usage of `delegate_method`, such as the following class UserPresenter delegate :id, to: :user attr_reader :user def initialize(user) @user = user end end class UserPresenterTest < ActiveSupport::TestCase subject { UserPresenter.new(User.new) } should delegate_method(:id).to(:user) end would error with something like: ArgumentError: wrong number of arguments (0 for 1) This happens because the `should` method in shoulda-context asks the DelegateMethodMatcher object for its description so that it can use it to give the test a name and create a method that Test::Unit can run. Now, the matcher's description needs a subject in order to determine whether a class or an instance is being tested here -- if a class is being tested the description will be "should delegate #id to \#user object", if a class then "should delegate .id to .user object". Unfortunately the matcher doesn't know what the subject is before its #description method is called -- it only knows about this when it gets evaluated. Within the matcher we do have access to the current context class, so we could read the subject block off of it and evaluate it. However, in order to properly do this we also need access to the instance of the test itself, which we do not have until the matcher is evaluated (by which point it's too late). Since there's really no way to solve this problem apart from rewriting a lot of shoulda-context, and since often times your subject is an instance and not a class, just assume it's an instance in this case.
This commit is contained in:
parent
db7723d925
commit
c683aed2f0
6 changed files with 128 additions and 7 deletions
47
features/independent_matchers.feature
Normal file
47
features/independent_matchers.feature
Normal file
|
@ -0,0 +1,47 @@
|
|||
Feature: Independent matchers
|
||||
Background:
|
||||
When I generate a new Ruby application
|
||||
|
||||
Scenario: A Ruby application that uses Minitest and the delegate_method matcher
|
||||
When I add Minitest to the project
|
||||
And I write to "lib/post_office.rb" with:
|
||||
"""
|
||||
class PostOffice
|
||||
end
|
||||
"""
|
||||
And I write to "lib/courier.rb" with:
|
||||
"""
|
||||
require "forwardable"
|
||||
|
||||
class Courier
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :post_office, :deliver
|
||||
|
||||
attr_reader :post_office
|
||||
|
||||
def initialize(post_office)
|
||||
@post_office = post_office
|
||||
end
|
||||
end
|
||||
"""
|
||||
And I write a Minitest test to "test/courier_test.rb" with:
|
||||
"""
|
||||
require "test_helper"
|
||||
require "courier"
|
||||
require "post_office"
|
||||
|
||||
class CourierTest < {{MINITEST_TEST_CASE_CLASS}}
|
||||
subject { Courier.new(post_office) }
|
||||
|
||||
should delegate_method(:deliver).to(:post_office)
|
||||
|
||||
def post_office
|
||||
PostOffice.new
|
||||
end
|
||||
end
|
||||
"""
|
||||
And I set the "TESTOPTS" environment variable to "-v"
|
||||
And I successfully run `bundle exec ruby -I lib -I test test/courier_test.rb`
|
||||
Then the output should indicate that 1 test was run
|
||||
And the output should contain "Courier should delegate #deliver to #post_office object"
|
|
@ -24,6 +24,11 @@ Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
|
|||
EOT
|
||||
end
|
||||
|
||||
When /I write a Minitest test to "([^"]+)" with:/ do |path, contents|
|
||||
contents.sub!('{{MINITEST_TEST_CASE_CLASS}}', minitest_test_case_class)
|
||||
write_file(path, contents)
|
||||
end
|
||||
|
||||
When 'I add Test::Unit to the project' do
|
||||
steps %{
|
||||
When I configure the application to use shoulda-context
|
||||
|
|
21
features/support/minitest_helpers.rb
Normal file
21
features/support/minitest_helpers.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module MinitestHelpers
|
||||
def minitest_test_case_class
|
||||
if minitest_gte_5?
|
||||
'Minitest::Test'
|
||||
else
|
||||
'MiniTest::Unit::TestCase'
|
||||
end
|
||||
end
|
||||
|
||||
def minitest_gte_5?
|
||||
if minitest_version
|
||||
Gem::Requirement.new('>= 5').satisfied_by?(minitest_version)
|
||||
end
|
||||
end
|
||||
|
||||
def minitest_version
|
||||
Bundler.definition.specs['minitest'][0].version
|
||||
end
|
||||
end
|
||||
|
||||
World(MinitestHelpers)
|
|
@ -1,9 +1,10 @@
|
|||
require 'shoulda/matchers/assertion_error'
|
||||
require 'shoulda/matchers/doublespeak'
|
||||
require 'shoulda/matchers/error'
|
||||
require 'shoulda/matchers/matcher_context'
|
||||
require 'shoulda/matchers/rails_shim'
|
||||
require 'shoulda/matchers/warn'
|
||||
require 'shoulda/matchers/version'
|
||||
require 'shoulda/matchers/warn'
|
||||
|
||||
require 'shoulda/matchers/independent'
|
||||
|
||||
|
|
|
@ -108,14 +108,13 @@ module Shoulda
|
|||
# @return [DelegateMethodMatcher]
|
||||
#
|
||||
def delegate_method(delegating_method)
|
||||
DelegateMethodMatcher.new(delegating_method, self)
|
||||
DelegateMethodMatcher.new(delegating_method).in_context(self)
|
||||
end
|
||||
|
||||
# @private
|
||||
class DelegateMethodMatcher
|
||||
def initialize(delegating_method, context)
|
||||
def initialize(delegating_method)
|
||||
@delegating_method = delegating_method
|
||||
@context = context
|
||||
|
||||
@method_on_target = @delegating_method
|
||||
@target_double = Doublespeak::ObjectDouble.new
|
||||
|
@ -126,6 +125,11 @@ module Shoulda
|
|||
@subject_double_collection = nil
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = MatcherContext.new(context)
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
|
||||
|
@ -192,11 +196,19 @@ module Shoulda
|
|||
:target_method
|
||||
|
||||
def subject
|
||||
@subject || context.subject
|
||||
@subject
|
||||
end
|
||||
|
||||
def subject_is_a_class?
|
||||
if @subject
|
||||
@subject.is_a?(Class)
|
||||
else
|
||||
context.subject_is_a_class?
|
||||
end
|
||||
end
|
||||
|
||||
def class_under_test
|
||||
if subject.is_a?(Class)
|
||||
if subject_is_a_class?
|
||||
subject
|
||||
else
|
||||
subject.class
|
||||
|
@ -230,7 +242,7 @@ module Shoulda
|
|||
end
|
||||
|
||||
def class_or_instance_method_indicator
|
||||
if subject.is_a?(Class)
|
||||
if subject_is_a_class?
|
||||
'.'
|
||||
else
|
||||
'#'
|
||||
|
|
35
lib/shoulda/matchers/matcher_context.rb
Normal file
35
lib/shoulda/matchers/matcher_context.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
class MatcherContext
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
def subject_is_a_class?
|
||||
if inside_a_shoulda_context_project? && outside_a_should_block?
|
||||
assume_that_subject_is_not_a_class
|
||||
else
|
||||
context.subject.is_a?(Class)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :context
|
||||
|
||||
private
|
||||
|
||||
def inside_a_shoulda_context_project?
|
||||
defined?(Shoulda::Context)
|
||||
end
|
||||
|
||||
def outside_a_should_block?
|
||||
context.is_a?(Class)
|
||||
end
|
||||
|
||||
def assume_that_subject_is_not_a_class
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue