rspec-expectations 3.5.0 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +138 -2
- data/README.md +37 -20
- data/lib/rspec/expectations.rb +2 -1
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +14 -0
- data/lib/rspec/expectations/expectation_target.rb +2 -2
- data/lib/rspec/expectations/fail_with.rb +9 -1
- data/lib/rspec/expectations/handler.rb +2 -2
- data/lib/rspec/expectations/minitest_integration.rb +1 -1
- data/lib/rspec/expectations/syntax.rb +2 -2
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +97 -97
- data/lib/rspec/matchers/built_in/all.rb +1 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
- data/lib/rspec/matchers/built_in/be.rb +2 -2
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/change.rb +127 -53
- data/lib/rspec/matchers/built_in/compound.rb +6 -2
- data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
- data/lib/rspec/matchers/built_in/exist.rb +5 -1
- data/lib/rspec/matchers/built_in/has.rb +1 -1
- data/lib/rspec/matchers/built_in/include.rb +6 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
- data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
- data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
- data/lib/rspec/matchers/built_in/yield.rb +43 -30
- data/lib/rspec/matchers/composable.rb +6 -20
- data/lib/rspec/matchers/dsl.rb +72 -4
- data/lib/rspec/matchers/english_phrasing.rb +3 -3
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- metadata +22 -18
- metadata.gz.sig +0 -0
@@ -14,7 +14,11 @@ module RSpec
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def match(expected, actual)
|
17
|
-
actual.instance_of?
|
17
|
+
actual.instance_of?(expected)
|
18
|
+
rescue NoMethodError
|
19
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
20
|
+
"the actual object responds to #instance_of? method " \
|
21
|
+
"but a `NoMethodError` was encountered instead."
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -8,7 +8,11 @@ module RSpec
|
|
8
8
|
private
|
9
9
|
|
10
10
|
def match(expected, actual)
|
11
|
-
actual.kind_of?
|
11
|
+
actual.kind_of?(expected)
|
12
|
+
rescue NoMethodError
|
13
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
14
|
+
"the actual object responds to #kind_of? method " \
|
15
|
+
"but a `NoMethodError` was encountered instead."
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -8,7 +8,7 @@ module RSpec
|
|
8
8
|
# @api public
|
9
9
|
# Specifies the delta of the expected change.
|
10
10
|
def by(expected_delta)
|
11
|
-
ChangeRelatively.new(
|
11
|
+
ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta|
|
12
12
|
values_match?(expected_delta, actual_delta)
|
13
13
|
end
|
14
14
|
end
|
@@ -16,7 +16,7 @@ module RSpec
|
|
16
16
|
# @api public
|
17
17
|
# Specifies a minimum delta of the expected change.
|
18
18
|
def by_at_least(minimum)
|
19
|
-
ChangeRelatively.new(
|
19
|
+
ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta|
|
20
20
|
actual_delta >= minimum
|
21
21
|
end
|
22
22
|
end
|
@@ -24,7 +24,7 @@ module RSpec
|
|
24
24
|
# @api public
|
25
25
|
# Specifies a maximum delta of the expected change.
|
26
26
|
def by_at_most(maximum)
|
27
|
-
ChangeRelatively.new(
|
27
|
+
ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta|
|
28
28
|
actual_delta <= maximum
|
29
29
|
end
|
30
30
|
end
|
@@ -32,47 +32,44 @@ module RSpec
|
|
32
32
|
# @api public
|
33
33
|
# Specifies the new value you expect.
|
34
34
|
def to(value)
|
35
|
-
ChangeToValue.new(
|
35
|
+
ChangeToValue.new(change_details, value)
|
36
36
|
end
|
37
37
|
|
38
38
|
# @api public
|
39
39
|
# Specifies the original value.
|
40
40
|
def from(value)
|
41
|
-
ChangeFromValue.new(
|
41
|
+
ChangeFromValue.new(change_details, value)
|
42
42
|
end
|
43
43
|
|
44
44
|
# @private
|
45
45
|
def matches?(event_proc)
|
46
|
-
@event_proc = event_proc
|
47
|
-
return false unless Proc === event_proc
|
48
46
|
raise_block_syntax_error if block_given?
|
49
|
-
|
50
|
-
@change_details.changed?
|
47
|
+
perform_change(event_proc) && change_details.changed?
|
51
48
|
end
|
52
49
|
|
53
50
|
def does_not_match?(event_proc)
|
54
51
|
raise_block_syntax_error if block_given?
|
55
|
-
|
52
|
+
perform_change(event_proc) && !change_details.changed?
|
56
53
|
end
|
57
54
|
|
58
55
|
# @api private
|
59
56
|
# @return [String]
|
60
57
|
def failure_message
|
61
|
-
"expected #{
|
58
|
+
"expected #{change_details.value_representation} to have changed, " \
|
62
59
|
"but #{positive_failure_reason}"
|
63
60
|
end
|
64
61
|
|
65
62
|
# @api private
|
66
63
|
# @return [String]
|
67
64
|
def failure_message_when_negated
|
68
|
-
"expected #{
|
65
|
+
"expected #{change_details.value_representation} not to have changed, " \
|
69
66
|
"but #{negative_failure_reason}"
|
70
67
|
end
|
71
68
|
|
72
69
|
# @api private
|
73
70
|
# @return [String]
|
74
71
|
def description
|
75
|
-
"change #{
|
72
|
+
"change #{change_details.value_representation}"
|
76
73
|
end
|
77
74
|
|
78
75
|
# @private
|
@@ -83,7 +80,25 @@ module RSpec
|
|
83
80
|
private
|
84
81
|
|
85
82
|
def initialize(receiver=nil, message=nil, &block)
|
86
|
-
@
|
83
|
+
@receiver = receiver
|
84
|
+
@message = message
|
85
|
+
@block = block
|
86
|
+
end
|
87
|
+
|
88
|
+
def change_details
|
89
|
+
@change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def perform_change(event_proc)
|
93
|
+
@event_proc = event_proc
|
94
|
+
change_details.perform_change(event_proc) do |actual_before|
|
95
|
+
# pre-compute values derived from the `before` value before the
|
96
|
+
# mutation is applied, in case the specified mutation is mutation
|
97
|
+
# of a single object (rather than a changing what object a method
|
98
|
+
# returns). We need to cache these values before the `before` value
|
99
|
+
# they are based on potentially gets mutated.
|
100
|
+
@actual_before_description = description_of(actual_before)
|
101
|
+
end
|
87
102
|
end
|
88
103
|
|
89
104
|
def raise_block_syntax_error
|
@@ -93,13 +108,13 @@ module RSpec
|
|
93
108
|
|
94
109
|
def positive_failure_reason
|
95
110
|
return "was not given a block" unless Proc === @event_proc
|
96
|
-
"is still #{
|
111
|
+
"is still #{@actual_before_description}"
|
97
112
|
end
|
98
113
|
|
99
114
|
def negative_failure_reason
|
100
115
|
return "was not given a block" unless Proc === @event_proc
|
101
|
-
"did change from #{
|
102
|
-
"to #{description_of
|
116
|
+
"did change from #{@actual_before_description} " \
|
117
|
+
"to #{description_of change_details.actual_after}"
|
103
118
|
end
|
104
119
|
end
|
105
120
|
|
@@ -115,7 +130,7 @@ module RSpec
|
|
115
130
|
|
116
131
|
# @private
|
117
132
|
def failure_message
|
118
|
-
"expected #{@change_details.
|
133
|
+
"expected #{@change_details.value_representation} to have changed " \
|
119
134
|
"#{@relativity.to_s.tr('_', ' ')} " \
|
120
135
|
"#{description_of @expected_delta}, but #{failure_reason}"
|
121
136
|
end
|
@@ -123,9 +138,7 @@ module RSpec
|
|
123
138
|
# @private
|
124
139
|
def matches?(event_proc)
|
125
140
|
@event_proc = event_proc
|
126
|
-
|
127
|
-
@change_details.perform_change(event_proc)
|
128
|
-
@comparer.call(@change_details.actual_delta)
|
141
|
+
@change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta)
|
129
142
|
end
|
130
143
|
|
131
144
|
# @private
|
@@ -136,7 +149,7 @@ module RSpec
|
|
136
149
|
|
137
150
|
# @private
|
138
151
|
def description
|
139
|
-
"change #{@change_details.
|
152
|
+
"change #{@change_details.value_representation} " \
|
140
153
|
"#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
|
141
154
|
end
|
142
155
|
|
@@ -167,21 +180,18 @@ module RSpec
|
|
167
180
|
|
168
181
|
# @private
|
169
182
|
def matches?(event_proc)
|
170
|
-
|
171
|
-
return false unless Proc === event_proc
|
172
|
-
@change_details.perform_change(event_proc)
|
173
|
-
@change_details.changed? && matches_before? && matches_after?
|
183
|
+
perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after?
|
174
184
|
end
|
175
185
|
|
176
186
|
# @private
|
177
187
|
def description
|
178
|
-
"change #{@change_details.
|
188
|
+
"change #{@change_details.value_representation} #{change_description}"
|
179
189
|
end
|
180
190
|
|
181
191
|
# @private
|
182
192
|
def failure_message
|
183
193
|
return not_given_a_block_failure unless Proc === @event_proc
|
184
|
-
return before_value_failure unless matches_before
|
194
|
+
return before_value_failure unless @matches_before
|
185
195
|
return did_not_change_failure unless @change_details.changed?
|
186
196
|
after_value_failure
|
187
197
|
end
|
@@ -193,8 +203,17 @@ module RSpec
|
|
193
203
|
|
194
204
|
private
|
195
205
|
|
196
|
-
def
|
197
|
-
|
206
|
+
def perform_change(event_proc)
|
207
|
+
@event_proc = event_proc
|
208
|
+
@change_details.perform_change(event_proc) do |actual_before|
|
209
|
+
# pre-compute values derived from the `before` value before the
|
210
|
+
# mutation is applied, in case the specified mutation is mutation
|
211
|
+
# of a single object (rather than a changing what object a method
|
212
|
+
# returns). We need to cache these values before the `before` value
|
213
|
+
# they are based on potentially gets mutated.
|
214
|
+
@matches_before = values_match?(@expected_before, actual_before)
|
215
|
+
@actual_before_description = description_of(actual_before)
|
216
|
+
end
|
198
217
|
end
|
199
218
|
|
200
219
|
def matches_after?
|
@@ -202,30 +221,30 @@ module RSpec
|
|
202
221
|
end
|
203
222
|
|
204
223
|
def before_value_failure
|
205
|
-
"expected #{@change_details.
|
224
|
+
"expected #{@change_details.value_representation} " \
|
206
225
|
"to have initially been #{description_of @expected_before}, " \
|
207
|
-
"but was #{
|
226
|
+
"but was #{@actual_before_description}"
|
208
227
|
end
|
209
228
|
|
210
229
|
def after_value_failure
|
211
|
-
"expected #{@change_details.
|
230
|
+
"expected #{@change_details.value_representation} " \
|
212
231
|
"to have changed to #{description_of @expected_after}, " \
|
213
232
|
"but is now #{description_of @change_details.actual_after}"
|
214
233
|
end
|
215
234
|
|
216
235
|
def did_not_change_failure
|
217
|
-
"expected #{@change_details.
|
236
|
+
"expected #{@change_details.value_representation} " \
|
218
237
|
"to have changed #{change_description}, but did not change"
|
219
238
|
end
|
220
239
|
|
221
240
|
def did_change_failure
|
222
|
-
"expected #{@change_details.
|
223
|
-
"did change from #{
|
241
|
+
"expected #{@change_details.value_representation} not to have changed, but " \
|
242
|
+
"did change from #{@actual_before_description} " \
|
224
243
|
"to #{description_of @change_details.actual_after}"
|
225
244
|
end
|
226
245
|
|
227
246
|
def not_given_a_block_failure
|
228
|
-
"expected #{@change_details.
|
247
|
+
"expected #{@change_details.value_representation} to have changed " \
|
229
248
|
"#{change_description}, but was not given a block"
|
230
249
|
end
|
231
250
|
end
|
@@ -254,16 +273,13 @@ module RSpec
|
|
254
273
|
"is not supported"
|
255
274
|
end
|
256
275
|
|
257
|
-
|
258
|
-
return false unless Proc === event_proc
|
259
|
-
@change_details.perform_change(event_proc)
|
260
|
-
!@change_details.changed? && matches_before?
|
276
|
+
perform_change(event_proc) && !@change_details.changed? && @matches_before
|
261
277
|
end
|
262
278
|
|
263
279
|
# @private
|
264
280
|
def failure_message_when_negated
|
265
281
|
return not_given_a_block_failure unless Proc === @event_proc
|
266
|
-
return before_value_failure unless matches_before
|
282
|
+
return before_value_failure unless @matches_before
|
267
283
|
did_change_failure
|
268
284
|
end
|
269
285
|
|
@@ -306,10 +322,22 @@ module RSpec
|
|
306
322
|
|
307
323
|
# @private
|
308
324
|
# Encapsulates the details of the before/after values.
|
325
|
+
#
|
326
|
+
# Note that this class exposes the `actual_after` value, to allow the
|
327
|
+
# matchers above to derive failure messages, etc from the value on demand
|
328
|
+
# as needed, but it intentionally does _not_ expose the `actual_before`
|
329
|
+
# value. Some usages of the `change` matcher mutate a specific object
|
330
|
+
# returned by the value proc, which means that failure message snippets,
|
331
|
+
# etc, which are derived from the `before` value may not be accurate if
|
332
|
+
# they are lazily computed as needed. We must pre-compute them before
|
333
|
+
# applying the change in the `expect` block. To ensure that all `change`
|
334
|
+
# matchers do that properly, we do not expose the `actual_before` value.
|
335
|
+
# Instead, matchers must pass a block to `perform_change`, which yields
|
336
|
+
# the `actual_before` value before applying the change.
|
309
337
|
class ChangeDetails
|
310
|
-
attr_reader :
|
338
|
+
attr_reader :actual_after
|
311
339
|
|
312
|
-
def initialize(receiver=nil, message=nil, &block)
|
340
|
+
def initialize(matcher_name, receiver=nil, message=nil, &block)
|
313
341
|
if receiver && !message
|
314
342
|
raise(
|
315
343
|
ArgumentError,
|
@@ -318,18 +346,51 @@ module RSpec
|
|
318
346
|
"You passed an object but no message."
|
319
347
|
)
|
320
348
|
end
|
321
|
-
|
322
|
-
@
|
349
|
+
|
350
|
+
@matcher_name = matcher_name
|
351
|
+
@receiver = receiver
|
352
|
+
@message = message
|
353
|
+
@value_proc = block
|
354
|
+
end
|
355
|
+
|
356
|
+
def value_representation
|
357
|
+
@value_representation ||=
|
358
|
+
if @message
|
359
|
+
"`#{message_notation(@receiver, @message)}`"
|
360
|
+
elsif (value_block_snippet = extract_value_block_snippet)
|
361
|
+
"`#{value_block_snippet}`"
|
362
|
+
else
|
363
|
+
'result'
|
364
|
+
end
|
323
365
|
end
|
324
366
|
|
325
367
|
def perform_change(event_proc)
|
326
368
|
@actual_before = evaluate_value_proc
|
369
|
+
@before_hash = @actual_before.hash
|
370
|
+
yield @actual_before if block_given?
|
371
|
+
|
372
|
+
return false unless Proc === event_proc
|
327
373
|
event_proc.call
|
374
|
+
|
328
375
|
@actual_after = evaluate_value_proc
|
376
|
+
@actual_hash = @actual_after.hash
|
377
|
+
true
|
329
378
|
end
|
330
379
|
|
331
380
|
def changed?
|
332
|
-
|
381
|
+
# Consider it changed if either:
|
382
|
+
#
|
383
|
+
# - The before/after values are unequal
|
384
|
+
# - The before/after values have different hash values
|
385
|
+
#
|
386
|
+
# The latter case specifically handles the case when the value proc
|
387
|
+
# returns the exact same object, but it has been mutated.
|
388
|
+
#
|
389
|
+
# Note that it is not sufficient to only check the hashes; it is
|
390
|
+
# possible for two values to be unequal (and of different classes)
|
391
|
+
# but to return the same hash value. Also, some objects may change
|
392
|
+
# their hash after being compared with `==`/`!=`.
|
393
|
+
@actual_before != @actual_after || @before_hash != @actual_hash
|
333
394
|
end
|
334
395
|
|
335
396
|
def actual_delta
|
@@ -339,13 +400,26 @@ module RSpec
|
|
339
400
|
private
|
340
401
|
|
341
402
|
def evaluate_value_proc
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
403
|
+
@value_proc ? @value_proc.call : @receiver.__send__(@message)
|
404
|
+
end
|
405
|
+
|
406
|
+
def message_notation(receiver, message)
|
407
|
+
case receiver
|
408
|
+
when Module
|
409
|
+
"#{receiver}.#{message}"
|
347
410
|
else
|
348
|
-
|
411
|
+
"#{Support.class_of(receiver)}##{message}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
416
|
+
def extract_value_block_snippet
|
417
|
+
return nil unless @value_proc
|
418
|
+
Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name)
|
419
|
+
end
|
420
|
+
else
|
421
|
+
def extract_value_block_snippet
|
422
|
+
nil
|
349
423
|
end
|
350
424
|
end
|
351
425
|
end
|
@@ -3,7 +3,6 @@ module RSpec
|
|
3
3
|
module BuiltIn
|
4
4
|
# @api private
|
5
5
|
# Base class for `and` and `or` compound matchers.
|
6
|
-
# rubocop:disable ClassLength
|
7
6
|
class Compound < BaseMatcher
|
8
7
|
# @private
|
9
8
|
attr_reader :matcher_1, :matcher_2, :evaluator
|
@@ -155,7 +154,12 @@ module RSpec
|
|
155
154
|
end
|
156
155
|
|
157
156
|
def matcher_matches?(matcher)
|
158
|
-
@match_results.fetch(matcher)
|
157
|
+
@match_results.fetch(matcher) do
|
158
|
+
raise ArgumentError, "Your #{matcher.description} has no match " \
|
159
|
+
"results, this can occur when an unexpected call stack or " \
|
160
|
+
"local jump occurs. Prehaps one of your matchers needs to " \
|
161
|
+
"declare `expects_call_stack_jump?` as `true`?"
|
162
|
+
end
|
159
163
|
end
|
160
164
|
|
161
165
|
private
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Matchers
|
3
3
|
module BuiltIn
|
4
|
+
# rubocop:disable ClassLength
|
4
5
|
# @api private
|
5
6
|
# Provides the implementation for `contain_exactly` and `match_array`.
|
6
7
|
# Not intended to be instantiated directly.
|
@@ -85,10 +86,10 @@ module RSpec
|
|
85
86
|
def convert_actual_to_an_array
|
86
87
|
if actual.respond_to?(:to_ary)
|
87
88
|
@actual = actual.to_ary
|
88
|
-
elsif
|
89
|
+
elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
|
89
90
|
@actual = actual.to_a
|
90
91
|
else
|
91
|
-
|
92
|
+
false
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -98,6 +99,19 @@ module RSpec
|
|
98
99
|
array
|
99
100
|
end
|
100
101
|
|
102
|
+
if RUBY_VERSION == "1.8.7"
|
103
|
+
def to_a_disallowed?(object)
|
104
|
+
case object
|
105
|
+
when NilClass, String then true
|
106
|
+
else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
|
107
|
+
end
|
108
|
+
end
|
109
|
+
else
|
110
|
+
def to_a_disallowed?(object)
|
111
|
+
NilClass === object
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
101
115
|
def missing_items
|
102
116
|
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
|
103
117
|
expected[index]
|
@@ -162,6 +176,7 @@ module RSpec
|
|
162
176
|
#
|
163
177
|
# @private
|
164
178
|
class PairingsMaximizer
|
179
|
+
# @private
|
165
180
|
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
|
166
181
|
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
|
167
182
|
def worse_than?(other)
|
@@ -281,6 +296,7 @@ module RSpec
|
|
281
296
|
end
|
282
297
|
end
|
283
298
|
end
|
299
|
+
# rubocop:enable ClassLength
|
284
300
|
end
|
285
301
|
end
|
286
302
|
end
|