rspec-sleeping_king_studios 2.1.1 → 2.2.0.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/DEVELOPMENT.md +26 -17
- data/LICENSE +1 -1
- data/README.md +198 -19
- data/lib/rspec/sleeping_king_studios/configuration.rb +63 -2
- data/lib/rspec/sleeping_king_studios/examples/property_examples.rb +115 -27
- data/lib/rspec/sleeping_king_studios/examples/rspec_matcher_examples.rb +66 -51
- data/lib/rspec/sleeping_king_studios/matchers/active_model/have_errors.rb +8 -269
- data/lib/rspec/sleeping_king_studios/matchers/active_model/have_errors_matcher.rb +268 -0
- data/lib/rspec/sleeping_king_studios/matchers/base_matcher.rb +5 -15
- data/lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of.rb +3 -63
- data/lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of_matcher.rb +65 -0
- data/lib/rspec/sleeping_king_studios/matchers/built_in/include.rb +3 -108
- data/lib/rspec/sleeping_king_studios/matchers/built_in/include_matcher.rb +134 -0
- data/lib/rspec/sleeping_king_studios/matchers/built_in/respond_to.rb +3 -258
- data/lib/rspec/sleeping_king_studios/matchers/built_in/respond_to_matcher.rb +116 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/alias_method.rb +11 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/alias_method_matcher.rb +107 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/be_boolean.rb +9 -36
- data/lib/rspec/sleeping_king_studios/matchers/core/be_boolean_matcher.rb +37 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/construct.rb +3 -210
- data/lib/rspec/sleeping_king_studios/matchers/core/construct_matcher.rb +113 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/delegate_method.rb +11 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/delegate_method_matcher.rb +311 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_constant.rb +16 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_constant_matcher.rb +225 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_predicate.rb +11 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_predicate_matcher.rb +97 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_property.rb +3 -104
- data/lib/rspec/sleeping_king_studios/matchers/core/have_property_matcher.rb +108 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_reader.rb +3 -74
- data/lib/rspec/sleeping_king_studios/matchers/core/have_reader_matcher.rb +96 -0
- data/lib/rspec/sleeping_king_studios/matchers/core/have_writer.rb +4 -59
- data/lib/rspec/sleeping_king_studios/matchers/core/have_writer_matcher.rb +55 -0
- data/lib/rspec/sleeping_king_studios/matchers/description.rb +62 -0
- data/lib/rspec/sleeping_king_studios/matchers/macros.rb +32 -0
- data/lib/rspec/sleeping_king_studios/matchers/shared/match_parameters.rb +168 -66
- data/lib/rspec/sleeping_king_studios/matchers/shared/match_property.rb +25 -0
- data/lib/rspec/sleeping_king_studios/matchers.rb +0 -4
- data/lib/rspec/sleeping_king_studios/support/method_signature.rb +51 -0
- data/lib/rspec/sleeping_king_studios/support/method_signature_expectation.rb +158 -0
- data/lib/rspec/sleeping_king_studios/support.rb +9 -0
- data/lib/rspec/sleeping_king_studios/version.rb +10 -4
- metadata +46 -30
@@ -1,216 +1,9 @@
|
|
1
1
|
# spec/rspec/sleeping_king_studios/matchers/core/construct.rb
|
2
2
|
|
3
|
-
require 'rspec/sleeping_king_studios/matchers/
|
4
|
-
require 'rspec/sleeping_king_studios/matchers/
|
5
|
-
require 'rspec/sleeping_king_studios/matchers/shared/match_parameters'
|
6
|
-
require 'sleeping_king_studios/tools/enumerable_tools'
|
7
|
-
require 'sleeping_king_studios/tools/string_tools'
|
3
|
+
require 'rspec/sleeping_king_studios/matchers/core/construct_matcher'
|
4
|
+
require 'rspec/sleeping_king_studios/matchers/macros'
|
8
5
|
|
9
|
-
module RSpec::SleepingKingStudios::Matchers::
|
10
|
-
# Matcher for checking whether an object can be constructed via #new and
|
11
|
-
# #initialize, and the parameters accepted by #initialize.
|
12
|
-
#
|
13
|
-
# @since 1.0.0
|
14
|
-
class ConstructMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
|
15
|
-
include RSpec::SleepingKingStudios::Matchers::Shared::MatchParameters
|
16
|
-
include SleepingKingStudios::Tools::EnumerableTools
|
17
|
-
include SleepingKingStudios::Tools::StringTools
|
18
|
-
|
19
|
-
# (see BaseMatcher#description)
|
20
|
-
def description
|
21
|
-
expected_message = format_expected_arguments
|
22
|
-
"construct#{expected_message.empty? ? '' : " with #{expected_message}"}"
|
23
|
-
end # method description
|
24
|
-
|
25
|
-
# Checks if the object responds to :new. If so, allocates an instance and
|
26
|
-
# checks the parameters expected by #initialize against the expected
|
27
|
-
# parameters, if any.
|
28
|
-
#
|
29
|
-
# @param [Object] actual The object to check.
|
30
|
-
#
|
31
|
-
# @return [Boolean] True if the object responds to :new and accepts the
|
32
|
-
# specified parameters for #initialize; otherwise false.
|
33
|
-
def matches? actual
|
34
|
-
@actual = actual
|
35
|
-
@failing_method_reasons = {}
|
36
|
-
@actual.respond_to?(:new) &&
|
37
|
-
matches_arity?(actual) &&
|
38
|
-
matches_keywords?(actual)
|
39
|
-
end # method matches?
|
40
|
-
|
41
|
-
# Adds a parameter count expectation and/or one or more keyword
|
42
|
-
# expectations.
|
43
|
-
#
|
44
|
-
# @overload with(count)
|
45
|
-
# Adds a parameter count expectation and removes any keyword
|
46
|
-
# expectations.
|
47
|
-
#
|
48
|
-
# @param [Integer, Range] count The number of expected parameters.
|
49
|
-
#
|
50
|
-
# @return [ConstructMatcher] self
|
51
|
-
#
|
52
|
-
# @overload with(count, *keywords)
|
53
|
-
# Adds a parameter count expectation and one or more keyword
|
54
|
-
# expectations.
|
55
|
-
#
|
56
|
-
# @param [Integer, Range] count The number of expected parameters.
|
57
|
-
# @param [Array<String, Symbol>] keywords List of keyword arguments
|
58
|
-
# accepted by the method.
|
59
|
-
#
|
60
|
-
# @return [ConstructMatcher] self
|
61
|
-
#
|
62
|
-
# @overload with(*keywords)
|
63
|
-
# Removes a parameter count expectation (if any) and adds one or more
|
64
|
-
# keyword expectations.
|
65
|
-
#
|
66
|
-
# @param [Array<String, Symbol>] keywords List of keyword arguments
|
67
|
-
# accepted by the method.
|
68
|
-
#
|
69
|
-
# @return [ConstructMatcher] self
|
70
|
-
def with *keywords
|
71
|
-
@expected_arity = keywords.shift if Integer === keywords.first || Range === keywords.first
|
72
|
-
@expected_keywords = keywords
|
73
|
-
self
|
74
|
-
end # method with
|
75
|
-
|
76
|
-
# Adds an unlimited parameter count expectation, e.g. that the method
|
77
|
-
# supports splatted array arguments of the form *args.
|
78
|
-
#
|
79
|
-
# @return [RespondToMatcher] self
|
80
|
-
def with_unlimited_arguments
|
81
|
-
@expect_unlimited_arguments = true
|
82
|
-
|
83
|
-
self
|
84
|
-
end # method with_unlimited_arguments
|
85
|
-
alias_method :and_unlimited_arguments, :with_unlimited_arguments
|
86
|
-
|
87
|
-
# Adds one or more keyword expectations.
|
88
|
-
#
|
89
|
-
# @param [Array<String, Symbol>] keywords List of keyword arguments
|
90
|
-
# accepted by the method.
|
91
|
-
#
|
92
|
-
# @return [RespondToMatcher] self
|
93
|
-
def with_keywords *keywords
|
94
|
-
(@expected_keywords ||= []).concat(keywords)
|
95
|
-
|
96
|
-
self
|
97
|
-
end # method with_keywords
|
98
|
-
alias_method :and_keywords, :with_keywords
|
99
|
-
|
100
|
-
# Adds an arbitrary keyword expectation, e.g. that the method supports
|
101
|
-
# any keywords with splatted hash arguments of the form **kwargs.
|
102
|
-
def with_arbitrary_keywords
|
103
|
-
@expect_arbitrary_keywords = true
|
104
|
-
|
105
|
-
self
|
106
|
-
end # method with_arbitrary_keywords
|
107
|
-
alias_method :and_arbitrary_keywords, :with_arbitrary_keywords
|
108
|
-
|
109
|
-
# Convenience method for more fluent specs. Does nothing and returns self.
|
110
|
-
#
|
111
|
-
# @return [ConstructMatcher] self
|
112
|
-
#
|
113
|
-
# @since 2.0.0
|
114
|
-
def argument
|
115
|
-
self
|
116
|
-
end # method argument
|
117
|
-
alias_method :arguments, :argument
|
118
|
-
|
119
|
-
# (see BaseMatcher#failure_message)
|
120
|
-
def failure_message
|
121
|
-
message = "expected #{@actual.inspect} to be constructible"
|
122
|
-
message << " with arguments:\n#{format_errors}" if @actual.respond_to?(:new)
|
123
|
-
message
|
124
|
-
end # method failure_message
|
125
|
-
|
126
|
-
# (see BaseMatcher#failure_message_when_negated)
|
127
|
-
def failure_message_when_negated
|
128
|
-
message = "expected #{@actual.inspect} not to be constructible"
|
129
|
-
unless (formatted = format_expected_arguments).empty?
|
130
|
-
message << " with #{formatted}"
|
131
|
-
end # unless
|
132
|
-
message
|
133
|
-
end # method failure_message_when_negated
|
134
|
-
|
135
|
-
private
|
136
|
-
|
137
|
-
def matches_arity? actual
|
138
|
-
return true unless @expected_arity || @expect_unlimited_arguments
|
139
|
-
|
140
|
-
if result = check_method_arity(actual.allocate.method(:initialize), @expected_arity, expect_unlimited_arguments: @expect_unlimited_arguments)
|
141
|
-
@failing_method_reasons.update result
|
142
|
-
return false
|
143
|
-
end # if
|
144
|
-
|
145
|
-
true
|
146
|
-
end # method matches_arity?
|
147
|
-
|
148
|
-
def matches_keywords? actual
|
149
|
-
return true unless @expected_keywords ||
|
150
|
-
@expect_arbitrary_keywords ||
|
151
|
-
(@expected_arity && RUBY_VERSION >= "2.1.0")
|
152
|
-
|
153
|
-
if result = check_method_keywords(actual.allocate.method(:initialize), @expected_keywords, expect_arbitrary_keywords: @expect_arbitrary_keywords)
|
154
|
-
@failing_method_reasons.update result
|
155
|
-
return false
|
156
|
-
end # if
|
157
|
-
|
158
|
-
true
|
159
|
-
end # method matches_keywords?
|
160
|
-
|
161
|
-
def format_expected_arguments
|
162
|
-
messages = []
|
163
|
-
|
164
|
-
if !@expected_arity.nil?
|
165
|
-
messages << "#{@expected_arity.inspect} argument#{1 == @expected_arity ? "" : "s"}"
|
166
|
-
end # if
|
167
|
-
|
168
|
-
if @expect_unlimited_arguments
|
169
|
-
messages << 'unlimited arguments'
|
170
|
-
end # if
|
171
|
-
|
172
|
-
if !(@expected_keywords.nil? || @expected_keywords.empty?)
|
173
|
-
messages << "#{pluralize @expected_keywords.count, 'keyword', 'keywords'} #{humanize_list @expected_keywords.map(&:inspect)}"
|
174
|
-
end # if
|
175
|
-
|
176
|
-
if @expect_arbitrary_keywords
|
177
|
-
messages << 'arbitrary keywords'
|
178
|
-
end # if
|
179
|
-
|
180
|
-
humanize_list messages
|
181
|
-
end # method format_expected_arguments
|
182
|
-
|
183
|
-
def format_errors
|
184
|
-
reasons, messages = @failing_method_reasons, []
|
185
|
-
|
186
|
-
if hsh = reasons.fetch(:not_enough_args, false)
|
187
|
-
messages << " expected at least #{hsh[:count]} arguments, but received #{hsh[:arity]}"
|
188
|
-
elsif hsh = reasons.fetch(:too_many_args, false)
|
189
|
-
messages << " expected at most #{hsh[:count]} arguments, but received #{hsh[:arity]}"
|
190
|
-
end # if-elsif
|
191
|
-
|
192
|
-
if hsh = reasons.fetch(:expected_unlimited_arguments, false)
|
193
|
-
messages << " expected at most #{hsh[:count]} arguments, but received unlimited arguments"
|
194
|
-
end # if
|
195
|
-
|
196
|
-
if ary = reasons.fetch(:missing_keywords, false)
|
197
|
-
messages << " missing #{pluralize ary.count, 'keyword', 'keywords'} #{humanize_list ary.map(&:inspect)}"
|
198
|
-
end # if
|
199
|
-
|
200
|
-
if ary = reasons.fetch(:unexpected_keywords, false)
|
201
|
-
messages << " unexpected #{pluralize ary.count, 'keyword', 'keywords'} #{humanize_list ary.map(&:inspect)}"
|
202
|
-
end # if
|
203
|
-
|
204
|
-
if reasons.fetch(:expected_arbitrary_keywords, false)
|
205
|
-
messages << " expected arbitrary keywords"
|
206
|
-
end # if
|
207
|
-
|
208
|
-
messages.join "\n"
|
209
|
-
end # method format_errors
|
210
|
-
end # class
|
211
|
-
end # module
|
212
|
-
|
213
|
-
module RSpec::SleepingKingStudios::Matchers
|
6
|
+
module RSpec::SleepingKingStudios::Matchers::Macros
|
214
7
|
# @see RSpec::SleepingKingStudios::Matchers::Core::ConstructMatcher#matches?
|
215
8
|
def construct
|
216
9
|
RSpec::SleepingKingStudios::Matchers::Core::ConstructMatcher.new
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# lib/rspec/sleeping_king_studios/matchers/core/construct_matcher.rb
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/matchers/base_matcher'
|
4
|
+
require 'rspec/sleeping_king_studios/matchers/core'
|
5
|
+
require 'rspec/sleeping_king_studios/matchers/shared/match_parameters'
|
6
|
+
|
7
|
+
module RSpec::SleepingKingStudios::Matchers::Core
|
8
|
+
# Matcher for checking whether an object can be constructed via #new and
|
9
|
+
# #initialize, and the parameters accepted by #initialize.
|
10
|
+
#
|
11
|
+
# @since 1.0.0
|
12
|
+
class ConstructMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
|
13
|
+
include RSpec::SleepingKingStudios::Matchers::Shared::MatchParameters
|
14
|
+
|
15
|
+
# (see BaseMatcher#description)
|
16
|
+
def description
|
17
|
+
message = 'construct'
|
18
|
+
|
19
|
+
if method_signature_expectation?
|
20
|
+
message << ' ' << method_signature_expectation.description
|
21
|
+
end # if
|
22
|
+
|
23
|
+
message
|
24
|
+
end # method description
|
25
|
+
|
26
|
+
# Checks if the object responds to :new. If so, allocates an instance and
|
27
|
+
# checks the parameters expected by #initialize against the expected
|
28
|
+
# parameters, if any.
|
29
|
+
#
|
30
|
+
# @param [Object] actual The object to check.
|
31
|
+
#
|
32
|
+
# @return [Boolean] True if the object responds to :new and accepts the
|
33
|
+
# specified parameters for #initialize; otherwise false.
|
34
|
+
def matches? actual
|
35
|
+
@actual = actual
|
36
|
+
@failing_method_reasons = {}
|
37
|
+
|
38
|
+
unless @actual.respond_to?(:new, @include_all)
|
39
|
+
@failing_method_reasons = {
|
40
|
+
:does_not_respond_to_new => true
|
41
|
+
} # end hash
|
42
|
+
|
43
|
+
return false
|
44
|
+
end # unless
|
45
|
+
|
46
|
+
instance =
|
47
|
+
begin; actual.allocate; rescue NoMethodError; nil; end ||
|
48
|
+
begin; actual.new; rescue StandardError; nil; end
|
49
|
+
|
50
|
+
if instance.nil?
|
51
|
+
@failing_method_reasons = {
|
52
|
+
:unable_to_create_instance => true
|
53
|
+
} # end hash
|
54
|
+
|
55
|
+
return false
|
56
|
+
end # unless
|
57
|
+
|
58
|
+
constructor =
|
59
|
+
begin; instance.method(:initialize); rescue NameError; nil; end
|
60
|
+
|
61
|
+
unless constructor.is_a?(Method)
|
62
|
+
@failing_method_reasons = {
|
63
|
+
:constructor_is_not_a_method => true
|
64
|
+
} # end hash
|
65
|
+
|
66
|
+
return false
|
67
|
+
end # unless
|
68
|
+
|
69
|
+
return true unless method_signature_expectation?
|
70
|
+
|
71
|
+
unless check_method_signature(constructor)
|
72
|
+
@failing_method_reasons = method_signature_expectation.errors
|
73
|
+
|
74
|
+
return false
|
75
|
+
end # unless
|
76
|
+
|
77
|
+
true
|
78
|
+
end # method matches?
|
79
|
+
|
80
|
+
# (see BaseMatcher#failure_message)
|
81
|
+
def failure_message
|
82
|
+
message = "expected #{@actual.inspect} to be constructible"
|
83
|
+
|
84
|
+
if @failing_method_reasons.key?(:does_not_respond_to_new)
|
85
|
+
message << ", but #{@actual.inspect} does not respond to ::new"
|
86
|
+
elsif @failing_method_reasons.key?(:unable_to_create_instance)
|
87
|
+
message << ", but was unable to allocate an instance of #{@actual.inspect} with ::allocate or ::new"
|
88
|
+
elsif @failing_method_reasons.key?(:constructor_is_not_a_method)
|
89
|
+
message <<
|
90
|
+
", but was unable to reflect on constructor because :initialize is not a method on #{@actual.inspect}"
|
91
|
+
else
|
92
|
+
errors = @failing_method_reasons
|
93
|
+
|
94
|
+
# TODO: Replace this with ", but received arguments did not match "\
|
95
|
+
# " method signature:"
|
96
|
+
message << " with arguments:\n" << format_errors(errors)
|
97
|
+
end # if-elsif-else
|
98
|
+
|
99
|
+
message
|
100
|
+
end # method failure_message
|
101
|
+
|
102
|
+
# (see BaseMatcher#failure_message_when_negated)
|
103
|
+
def failure_message_when_negated
|
104
|
+
message = "expected #{@actual.inspect} not to be constructible"
|
105
|
+
|
106
|
+
if method_signature_expectation?
|
107
|
+
message << ' ' << method_signature_expectation.description
|
108
|
+
end # if
|
109
|
+
|
110
|
+
message
|
111
|
+
end # method failure_message_when_negated
|
112
|
+
end # class
|
113
|
+
end # module
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# lib/rspec/sleeping_king_studios/matchers/core/delegate_method.rb
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/matchers/core/delegate_method_matcher'
|
4
|
+
require 'rspec/sleeping_king_studios/matchers/macros'
|
5
|
+
|
6
|
+
module RSpec::SleepingKingStudios::Matchers::Macros
|
7
|
+
# @see RSpec::SleepingKingStudios::Matchers::Core::DelegateMethodMatcher#matches?
|
8
|
+
def delegate_method *method_names
|
9
|
+
RSpec::SleepingKingStudios::Matchers::Core::DelegateMethodMatcher.new *method_names
|
10
|
+
end # method delegate_method
|
11
|
+
end # module
|
@@ -0,0 +1,311 @@
|
|
1
|
+
# lib/rspec/sleeping_king_studios/matchers/core/delegate_method_matcher.rb
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/matchers/base_matcher'
|
4
|
+
|
5
|
+
require 'sleeping_king_studios/tools/array_tools'
|
6
|
+
|
7
|
+
module RSpec::SleepingKingStudios::Matchers::Core
|
8
|
+
# Matcher for testing whether an object delegates a method to the specified
|
9
|
+
# other object.
|
10
|
+
#
|
11
|
+
# @since 2.2.0
|
12
|
+
class DelegateMethodMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
|
13
|
+
include RSpec::Mocks::ExampleMethods
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
DEFAULT_EXPECTED_RETURN = Object.new.freeze
|
17
|
+
|
18
|
+
# @param expected [Array<String, Symbol>] The names of the methods that
|
19
|
+
# should be delegated to the target.
|
20
|
+
def initialize expected
|
21
|
+
@expected = expected
|
22
|
+
@expected_arguments = []
|
23
|
+
@expected_keywords = {}
|
24
|
+
@expected_block = false
|
25
|
+
@received_block = false
|
26
|
+
@expected_return_values = []
|
27
|
+
@received_return_values = []
|
28
|
+
@errors = {}
|
29
|
+
end # method initialize
|
30
|
+
|
31
|
+
attr_reader :expected
|
32
|
+
alias_method :method_name, :expected
|
33
|
+
|
34
|
+
# Specifies that the actual method, when called, will return the specified
|
35
|
+
# value. If more than one return value is specified, the method will be
|
36
|
+
# called one time for each return value, and on the Nth call must return
|
37
|
+
# the Nth specified return value.
|
38
|
+
#
|
39
|
+
# @param return_values [Array<Object>] The expected values to be returned
|
40
|
+
# from calling the method on the actual object.
|
41
|
+
#
|
42
|
+
# @return [DelegateMethodMatcher] self
|
43
|
+
def and_return *return_values
|
44
|
+
@expected_return_values = return_values
|
45
|
+
|
46
|
+
self
|
47
|
+
end # method and_return
|
48
|
+
|
49
|
+
# (see BaseMatcher#description)
|
50
|
+
def description
|
51
|
+
str = 'delegate method'
|
52
|
+
str << " :#{@expected}" if @expected
|
53
|
+
str << " to #{@target.inspect}" if @target
|
54
|
+
str
|
55
|
+
end # method description
|
56
|
+
|
57
|
+
# (see BaseMatcher#failure_message)
|
58
|
+
def failure_message
|
59
|
+
message =
|
60
|
+
"expected #{@actual.inspect} to delegate :#{@expected} to "\
|
61
|
+
"#{@target.inspect}"
|
62
|
+
|
63
|
+
if @errors.key?(:actual_does_not_respond_to)
|
64
|
+
message << ", but #{@actual.inspect} does not respond to :#{@expected}"
|
65
|
+
|
66
|
+
return message
|
67
|
+
end # if
|
68
|
+
|
69
|
+
if @errors.key?(:target_does_not_respond_to)
|
70
|
+
message << ", but #{@target.inspect} does not respond to :#{@expected}"
|
71
|
+
|
72
|
+
return message
|
73
|
+
end # if
|
74
|
+
|
75
|
+
if @errors.key?(:raised_exception)
|
76
|
+
exception = @errors[:raised_exception]
|
77
|
+
|
78
|
+
message << format_arguments << format_return_values <<
|
79
|
+
", but ##{@expected} raised #{exception.class.name}: "\
|
80
|
+
"#{exception.message}"
|
81
|
+
|
82
|
+
return message
|
83
|
+
end # if
|
84
|
+
|
85
|
+
if @errors.key?(:actual_does_not_delegate_to_target)
|
86
|
+
message <<
|
87
|
+
", but calling ##{@expected} on the object does not call "\
|
88
|
+
"##{@expected} on the delegate"
|
89
|
+
|
90
|
+
return message
|
91
|
+
end # if
|
92
|
+
|
93
|
+
message << format_arguments << format_return_values
|
94
|
+
|
95
|
+
if @errors.key?(:unexpected_arguments)
|
96
|
+
message << ", but #{@errors[:unexpected_arguments]}"
|
97
|
+
|
98
|
+
return message
|
99
|
+
end # if
|
100
|
+
|
101
|
+
if @errors.key?(:block_not_received)
|
102
|
+
message << ', but the block was not passed to the delegate'
|
103
|
+
|
104
|
+
return message
|
105
|
+
end # if
|
106
|
+
|
107
|
+
if @errors.key?(:unexpected_return)
|
108
|
+
values = @errors[:unexpected_return].map &:inspect
|
109
|
+
|
110
|
+
message << ', but returned ' <<
|
111
|
+
SleepingKingStudios::Tools::ArrayTools.humanize_list(values)
|
112
|
+
end # if
|
113
|
+
|
114
|
+
message
|
115
|
+
end # method failure_message
|
116
|
+
|
117
|
+
# (see BaseMatcher#failure_message_when_negated)
|
118
|
+
def failure_message_when_negated
|
119
|
+
message =
|
120
|
+
"expected #{@actual.inspect} not to delegate :#{@expected} to "\
|
121
|
+
"#{@target.inspect}"
|
122
|
+
|
123
|
+
message << format_arguments << format_return_values
|
124
|
+
end # method failure_message_when_negated
|
125
|
+
|
126
|
+
# (see BaseMatcher#matches?)
|
127
|
+
def matches? actual
|
128
|
+
super
|
129
|
+
|
130
|
+
raise ArgumentError.new('must specify a target') if @target.nil?
|
131
|
+
|
132
|
+
responds_to_method? && delegates_method?
|
133
|
+
end # method matches?
|
134
|
+
|
135
|
+
# Specifies the target object. The expected methods should be delegated
|
136
|
+
# from the actual object to the target.
|
137
|
+
#
|
138
|
+
# @param target [Object] The target object.
|
139
|
+
#
|
140
|
+
# @return [DelegateMethodMatcher] self
|
141
|
+
def to target
|
142
|
+
@target = target
|
143
|
+
|
144
|
+
self
|
145
|
+
end # method to
|
146
|
+
|
147
|
+
# Specifies that a block argument must be passed in to the target when
|
148
|
+
# calling the method on the actual object.
|
149
|
+
#
|
150
|
+
# @return [DelegateMethodMatcher] self
|
151
|
+
def with_a_block
|
152
|
+
@expected_block = true
|
153
|
+
|
154
|
+
self
|
155
|
+
end # method with_a_block
|
156
|
+
alias_method :and_a_block, :with_a_block
|
157
|
+
|
158
|
+
# Specifies a list of arguments. The provided arguments are passed in to the
|
159
|
+
# method call when calling the method on the actual object, and must be
|
160
|
+
# passed on to the target.
|
161
|
+
#
|
162
|
+
# @param arguments [Array<Object>] The arguments to be passed in to the
|
163
|
+
# method and which must be forwarded to the target.
|
164
|
+
#
|
165
|
+
# @return [DelegateMethodMatcher] self
|
166
|
+
def with_arguments *arguments
|
167
|
+
@expected_arguments = arguments
|
168
|
+
|
169
|
+
self
|
170
|
+
end # method with_arguments
|
171
|
+
alias_method :and_arguments, :with_arguments
|
172
|
+
|
173
|
+
# Specifies a hash of keywords and values. The provided keywords are passed
|
174
|
+
# in to the method call when calling the method on the actual object, and
|
175
|
+
# must be passed on to the target.
|
176
|
+
#
|
177
|
+
# @param keywords [Hash] The keywords to be passed in to the
|
178
|
+
# method and which must be forwarded to the target.
|
179
|
+
#
|
180
|
+
# @return [DelegateMethodMatcher] self
|
181
|
+
def with_keywords **keywords
|
182
|
+
@expected_keywords = keywords
|
183
|
+
|
184
|
+
self
|
185
|
+
end # method with_keywords
|
186
|
+
alias_method :and_keywords, :with_keywords
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def call_method arguments, expected_return = DEFAULT_EXPECTED_RETURN
|
191
|
+
if @expected_block
|
192
|
+
@received_block = false
|
193
|
+
block = ->(*args, **kwargs, &block) {}
|
194
|
+
|
195
|
+
return_value = @actual.send(@expected, *arguments, &block)
|
196
|
+
else
|
197
|
+
return_value = @actual.send(@expected, *arguments)
|
198
|
+
end
|
199
|
+
|
200
|
+
@received_return_values << return_value
|
201
|
+
|
202
|
+
if @expected_block && !@received_block
|
203
|
+
@errors[:block_not_received] = true
|
204
|
+
end # if
|
205
|
+
|
206
|
+
return if expected_return == DEFAULT_EXPECTED_RETURN
|
207
|
+
|
208
|
+
unless return_value == expected_return
|
209
|
+
@errors[:unexpected_return] = @received_return_values
|
210
|
+
end # unless
|
211
|
+
rescue StandardError => exception
|
212
|
+
@errors[:raised_exception] = exception
|
213
|
+
end # method call_method
|
214
|
+
|
215
|
+
def delegates_method?
|
216
|
+
stub_target!
|
217
|
+
|
218
|
+
args = @expected_arguments.dup
|
219
|
+
args << @expected_keywords unless @expected_keywords.empty?
|
220
|
+
|
221
|
+
if @expected_return_values.empty?
|
222
|
+
call_method(args)
|
223
|
+
else
|
224
|
+
@expected_return_values.each do |return_value|
|
225
|
+
call_method(args, return_value)
|
226
|
+
end # each
|
227
|
+
end # if-else
|
228
|
+
|
229
|
+
matcher = RSpec::Mocks::Matchers::HaveReceived.new(@expected)
|
230
|
+
matcher = matcher.with(*args) unless args.empty?
|
231
|
+
|
232
|
+
unless @expected_return_values.empty?
|
233
|
+
matcher = matcher.exactly(@expected_return_values.count).times
|
234
|
+
end # unless
|
235
|
+
|
236
|
+
unless matcher.matches? @target
|
237
|
+
if matcher.failure_message =~ /with unexpected arguments/
|
238
|
+
@errors[:unexpected_arguments] = matcher.failure_message
|
239
|
+
else
|
240
|
+
@errors[:actual_does_not_delegate_to_target] = true
|
241
|
+
end # if
|
242
|
+
end # unless
|
243
|
+
|
244
|
+
@errors.empty?
|
245
|
+
end # method delegates_method?
|
246
|
+
|
247
|
+
def format_arguments
|
248
|
+
fragments = []
|
249
|
+
|
250
|
+
unless @expected_arguments.empty?
|
251
|
+
args = SleepingKingStudios::Tools::ArrayTools.humanize_list(@expected_arguments.map &:inspect)
|
252
|
+
|
253
|
+
fragments << ('arguments ' << args)
|
254
|
+
end # unless
|
255
|
+
|
256
|
+
unless @expected_keywords.empty?
|
257
|
+
kwargs = @expected_keywords.map { |key, value| "#{key.inspect}=>#{value.inspect}" }
|
258
|
+
kwargs = SleepingKingStudios::Tools::ArrayTools.humanize_list(kwargs)
|
259
|
+
|
260
|
+
fragments << ('keywords ' << kwargs)
|
261
|
+
end # unless
|
262
|
+
|
263
|
+
if fragments.empty?
|
264
|
+
arguments = ' with no arguments'
|
265
|
+
else
|
266
|
+
arguments = ' with ' << fragments.join(' and ')
|
267
|
+
end # if-else
|
268
|
+
|
269
|
+
arguments << ' and yield a block' if @expected_block
|
270
|
+
|
271
|
+
arguments
|
272
|
+
end # method format_arguments
|
273
|
+
|
274
|
+
def format_return_values
|
275
|
+
return '' if @expected_return_values.empty?
|
276
|
+
|
277
|
+
values = @expected_return_values.map &:inspect
|
278
|
+
|
279
|
+
' and return ' << SleepingKingStudios::Tools::ArrayTools.humanize_list(values)
|
280
|
+
end # method format_return_values
|
281
|
+
|
282
|
+
def responds_to_method?
|
283
|
+
unless @actual.respond_to?(@expected)
|
284
|
+
@errors[:actual_does_not_respond_to] = true
|
285
|
+
end # unless
|
286
|
+
|
287
|
+
unless @target.respond_to?(@expected)
|
288
|
+
@errors[:target_does_not_respond_to] = true
|
289
|
+
end # unless
|
290
|
+
|
291
|
+
@errors.empty?
|
292
|
+
end # method responds_to_methods?
|
293
|
+
|
294
|
+
def stub_target!
|
295
|
+
original_method = @target.method(@expected)
|
296
|
+
|
297
|
+
receive_matcher = receive(@expected)
|
298
|
+
receive_matcher.with(any_args) do |*args, **kwargs, &block|
|
299
|
+
@received_block = !!block
|
300
|
+
|
301
|
+
if kwargs.empty?
|
302
|
+
original_method.call(*args, &block)
|
303
|
+
else
|
304
|
+
original_method.call(*args, **kwargs, &block)
|
305
|
+
end # if-else
|
306
|
+
end # matcher
|
307
|
+
|
308
|
+
allow(@target).to receive_matcher
|
309
|
+
end # metod stub_target!
|
310
|
+
end # class
|
311
|
+
end # module
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# lib/rspec/sleeping_king_studios/matchers/core/have_constant.rb
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/matchers/core/have_constant_matcher'
|
4
|
+
require 'rspec/sleeping_king_studios/matchers/macros'
|
5
|
+
|
6
|
+
module RSpec::SleepingKingStudios::Matchers::Macros
|
7
|
+
# @see RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher#matches?
|
8
|
+
def have_constant expected
|
9
|
+
RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher.new expected
|
10
|
+
end # method have_reader
|
11
|
+
|
12
|
+
# @see RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher#immutable
|
13
|
+
def have_immutable_constant expected
|
14
|
+
have_constant(expected).immutable
|
15
|
+
end # method have_reader
|
16
|
+
end # module
|