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.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +138 -2
  5. data/README.md +37 -20
  6. data/lib/rspec/expectations.rb +2 -1
  7. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  8. data/lib/rspec/expectations/configuration.rb +14 -0
  9. data/lib/rspec/expectations/expectation_target.rb +2 -2
  10. data/lib/rspec/expectations/fail_with.rb +9 -1
  11. data/lib/rspec/expectations/handler.rb +2 -2
  12. data/lib/rspec/expectations/minitest_integration.rb +1 -1
  13. data/lib/rspec/expectations/syntax.rb +2 -2
  14. data/lib/rspec/expectations/version.rb +1 -1
  15. data/lib/rspec/matchers.rb +97 -97
  16. data/lib/rspec/matchers/built_in/all.rb +1 -0
  17. data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
  18. data/lib/rspec/matchers/built_in/be.rb +2 -2
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  21. data/lib/rspec/matchers/built_in/change.rb +127 -53
  22. data/lib/rspec/matchers/built_in/compound.rb +6 -2
  23. data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
  24. data/lib/rspec/matchers/built_in/exist.rb +5 -1
  25. data/lib/rspec/matchers/built_in/has.rb +1 -1
  26. data/lib/rspec/matchers/built_in/include.rb +6 -0
  27. data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
  28. data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
  29. data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
  30. data/lib/rspec/matchers/built_in/yield.rb +43 -30
  31. data/lib/rspec/matchers/composable.rb +6 -20
  32. data/lib/rspec/matchers/dsl.rb +72 -4
  33. data/lib/rspec/matchers/english_phrasing.rb +3 -3
  34. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
  35. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  36. metadata +22 -18
  37. 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? expected
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? expected
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(@change_details, expected_delta, :by) do |actual_delta|
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(@change_details, minimum, :by_at_least) do |actual_delta|
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(@change_details, maximum, :by_at_most) do |actual_delta|
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(@change_details, value)
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(@change_details, value)
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
- @change_details.perform_change(event_proc)
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
- !matches?(event_proc) && Proc === event_proc
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 #{@change_details.message} to have changed, " \
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 #{@change_details.message} not to have changed, " \
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 #{@change_details.message}"
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
- @change_details = ChangeDetails.new(receiver, message, &block)
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 #{description_of @change_details.actual_before}"
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 #{description_of @change_details.actual_before} " \
102
- "to #{description_of @change_details.actual_after}"
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.message} to have changed " \
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
- return false unless Proc === event_proc
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.message} " \
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
- @event_proc = event_proc
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.message} #{change_description}"
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 matches_before?
197
- values_match?(@expected_before, @change_details.actual_before)
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.message} " \
224
+ "expected #{@change_details.value_representation} " \
206
225
  "to have initially been #{description_of @expected_before}, " \
207
- "but was #{description_of @change_details.actual_before}"
226
+ "but was #{@actual_before_description}"
208
227
  end
209
228
 
210
229
  def after_value_failure
211
- "expected #{@change_details.message} " \
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.message} " \
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.message} not to have changed, but " \
223
- "did change from #{description_of @change_details.actual_before} " \
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.message} to have changed " \
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
- @event_proc = event_proc
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 :message, :actual_before, :actual_after
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
- @message = message ? "##{message}" : "result"
322
- @value_proc = block || lambda { receiver.__send__(message) }
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
- @actual_before != @actual_after
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
- case val = @value_proc.call
343
- when IO # enumerable, but we don't want to dup it.
344
- val
345
- when Enumerable, String
346
- val.dup
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
- val
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 should_enumerate?(actual) && actual.respond_to?(:to_a)
89
+ elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
89
90
  @actual = actual.to_a
90
91
  else
91
- return false
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