rspec-expectations 3.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +5 -0
  4. data/.document +5 -0
  5. data/.yardopts +6 -0
  6. data/Changelog.md +1156 -0
  7. data/LICENSE.md +25 -0
  8. data/README.md +305 -0
  9. data/lib/rspec/expectations.rb +82 -0
  10. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  11. data/lib/rspec/expectations/configuration.rb +215 -0
  12. data/lib/rspec/expectations/expectation_target.rb +127 -0
  13. data/lib/rspec/expectations/fail_with.rb +39 -0
  14. data/lib/rspec/expectations/failure_aggregator.rb +194 -0
  15. data/lib/rspec/expectations/handler.rb +170 -0
  16. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  17. data/lib/rspec/expectations/syntax.rb +132 -0
  18. data/lib/rspec/expectations/version.rb +8 -0
  19. data/lib/rspec/matchers.rb +1034 -0
  20. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  21. data/lib/rspec/matchers/built_in.rb +52 -0
  22. data/lib/rspec/matchers/built_in/all.rb +86 -0
  23. data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
  24. data/lib/rspec/matchers/built_in/be.rb +288 -0
  25. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  26. data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
  27. data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
  28. data/lib/rspec/matchers/built_in/be_within.rb +72 -0
  29. data/lib/rspec/matchers/built_in/change.rb +428 -0
  30. data/lib/rspec/matchers/built_in/compound.rb +271 -0
  31. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  32. data/lib/rspec/matchers/built_in/cover.rb +24 -0
  33. data/lib/rspec/matchers/built_in/eq.rb +40 -0
  34. data/lib/rspec/matchers/built_in/eql.rb +34 -0
  35. data/lib/rspec/matchers/built_in/equal.rb +81 -0
  36. data/lib/rspec/matchers/built_in/exist.rb +90 -0
  37. data/lib/rspec/matchers/built_in/has.rb +103 -0
  38. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  39. data/lib/rspec/matchers/built_in/include.rb +149 -0
  40. data/lib/rspec/matchers/built_in/match.rb +106 -0
  41. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  42. data/lib/rspec/matchers/built_in/output.rb +200 -0
  43. data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
  44. data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
  45. data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
  46. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  47. data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
  48. data/lib/rspec/matchers/built_in/yield.rb +432 -0
  49. data/lib/rspec/matchers/composable.rb +171 -0
  50. data/lib/rspec/matchers/dsl.rb +527 -0
  51. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  52. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
  53. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  54. data/lib/rspec/matchers/generated_descriptions.rb +41 -0
  55. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  56. data/lib/rspec/matchers/matcher_protocol.rb +99 -0
  57. metadata +215 -0
  58. metadata.gz.sig +0 -0
@@ -0,0 +1,432 @@
1
+ RSpec::Support.require_rspec_support 'method_signature_verifier'
2
+
3
+ module RSpec
4
+ module Matchers
5
+ module BuiltIn
6
+ # @private
7
+ # Object that is yielded to `expect` when one of the
8
+ # yield matchers is used. Provides information about
9
+ # the yield behavior of the object-under-test.
10
+ class YieldProbe
11
+ def self.probe(block, &callback)
12
+ probe = new(block, &callback)
13
+ return probe unless probe.has_block?
14
+ probe.probe
15
+ end
16
+
17
+ attr_accessor :num_yields, :yielded_args
18
+
19
+ def initialize(block, &callback)
20
+ @block = block
21
+ @callback = callback || Proc.new {}
22
+ @used = false
23
+ self.num_yields = 0
24
+ self.yielded_args = []
25
+ end
26
+
27
+ def has_block?
28
+ Proc === @block
29
+ end
30
+
31
+ def probe
32
+ assert_valid_expect_block!
33
+ @block.call(self)
34
+ assert_used!
35
+ self
36
+ end
37
+
38
+ def to_proc
39
+ @used = true
40
+
41
+ probe = self
42
+ callback = @callback
43
+ Proc.new do |*args|
44
+ probe.num_yields += 1
45
+ probe.yielded_args << args
46
+ callback.call(*args)
47
+ nil # to indicate the block does not return a meaningful value
48
+ end
49
+ end
50
+
51
+ def single_yield_args
52
+ yielded_args.first
53
+ end
54
+
55
+ def yielded_once?(matcher_name)
56
+ case num_yields
57
+ when 1 then true
58
+ when 0 then false
59
+ else
60
+ raise "The #{matcher_name} matcher is not designed to be used with a " \
61
+ 'method that yields multiple times. Use the yield_successive_args ' \
62
+ 'matcher for that case.'
63
+ end
64
+ end
65
+
66
+ def assert_used!
67
+ return if @used
68
+ raise 'You must pass the argument yielded to your expect block on ' \
69
+ 'to the method-under-test as a block. It acts as a probe that ' \
70
+ 'allows the matcher to detect whether or not the method-under-test ' \
71
+ 'yields, and, if so, how many times, and what the yielded arguments ' \
72
+ 'are.'
73
+ end
74
+
75
+ if RUBY_VERSION.to_f > 1.8
76
+ def assert_valid_expect_block!
77
+ block_signature = RSpec::Support::BlockSignature.new(@block)
78
+ return if RSpec::Support::StrictSignatureVerifier.new(block_signature, [self]).valid?
79
+ raise 'Your expect block must accept an argument to be used with this ' \
80
+ 'matcher. Pass the argument as a block on to the method you are testing.'
81
+ end
82
+ else
83
+ # :nocov:
84
+ # On 1.8.7, `lambda { }.arity` and `lambda { |*a| }.arity` both return -1,
85
+ # so we can't distinguish between accepting no args and an arg splat.
86
+ # It's OK to skip, this, though; it just provides a nice error message
87
+ # when the user forgets to accept an arg in their block. They'll still get
88
+ # the `assert_used!` error message from above, which is sufficient.
89
+ def assert_valid_expect_block!
90
+ # nothing to do
91
+ end
92
+ # :nocov:
93
+ end
94
+ end
95
+
96
+ # @api private
97
+ # Provides the implementation for `yield_control`.
98
+ # Not intended to be instantiated directly.
99
+ class YieldControl < BaseMatcher
100
+ def initialize
101
+ at_least(:once)
102
+ end
103
+
104
+ # @api public
105
+ # Specifies that the method is expected to yield once.
106
+ def once
107
+ exactly(1)
108
+ self
109
+ end
110
+
111
+ # @api public
112
+ # Specifies that the method is expected to yield twice.
113
+ def twice
114
+ exactly(2)
115
+ self
116
+ end
117
+
118
+ # @api public
119
+ # Specifies that the method is expected to yield thrice.
120
+ def thrice
121
+ exactly(3)
122
+ self
123
+ end
124
+
125
+ # @api public
126
+ # Specifies that the method is expected to yield the given number of times.
127
+ def exactly(number)
128
+ set_expected_yields_count(:==, number)
129
+ self
130
+ end
131
+
132
+ # @api public
133
+ # Specifies the maximum number of times the method is expected to yield
134
+ def at_most(number)
135
+ set_expected_yields_count(:<=, number)
136
+ self
137
+ end
138
+
139
+ # @api public
140
+ # Specifies the minimum number of times the method is expected to yield
141
+ def at_least(number)
142
+ set_expected_yields_count(:>=, number)
143
+ self
144
+ end
145
+
146
+ # @api public
147
+ # No-op. Provides syntactic sugar.
148
+ def times
149
+ self
150
+ end
151
+
152
+ # @private
153
+ def matches?(block)
154
+ @probe = YieldProbe.probe(block)
155
+ return false unless @probe.has_block?
156
+
157
+ @probe.num_yields.__send__(@expectation_type, @expected_yields_count)
158
+ end
159
+
160
+ # @private
161
+ def does_not_match?(block)
162
+ !matches?(block) && @probe.has_block?
163
+ end
164
+
165
+ # @api private
166
+ # @return [String]
167
+ def failure_message
168
+ 'expected given block to yield control' + failure_reason
169
+ end
170
+
171
+ # @api private
172
+ # @return [String]
173
+ def failure_message_when_negated
174
+ 'expected given block not to yield control' + failure_reason
175
+ end
176
+
177
+ # @private
178
+ def supports_block_expectations?
179
+ true
180
+ end
181
+
182
+ private
183
+
184
+ def set_expected_yields_count(relativity, n)
185
+ @expectation_type = relativity
186
+ @expected_yields_count = case n
187
+ when Numeric then n
188
+ when :once then 1
189
+ when :twice then 2
190
+ when :thrice then 3
191
+ end
192
+ end
193
+
194
+ def failure_reason
195
+ return ' but was not a block' unless @probe.has_block?
196
+ return '' unless @expected_yields_count
197
+ " #{human_readable_expectation_type}#{human_readable_count(@expected_yields_count)}" \
198
+ " but yielded #{human_readable_count(@probe.num_yields)}"
199
+ end
200
+
201
+ def human_readable_expectation_type
202
+ case @expectation_type
203
+ when :<= then 'at most '
204
+ when :>= then 'at least '
205
+ else ''
206
+ end
207
+ end
208
+
209
+ def human_readable_count(count)
210
+ case count
211
+ when 1 then 'once'
212
+ when 2 then 'twice'
213
+ else "#{count} times"
214
+ end
215
+ end
216
+ end
217
+
218
+ # @api private
219
+ # Provides the implementation for `yield_with_no_args`.
220
+ # Not intended to be instantiated directly.
221
+ class YieldWithNoArgs < BaseMatcher
222
+ # @private
223
+ def matches?(block)
224
+ @probe = YieldProbe.probe(block)
225
+ return false unless @probe.has_block?
226
+ @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty?
227
+ end
228
+
229
+ # @private
230
+ def does_not_match?(block)
231
+ !matches?(block) && @probe.has_block?
232
+ end
233
+
234
+ # @private
235
+ def failure_message
236
+ "expected given block to yield with no arguments, but #{positive_failure_reason}"
237
+ end
238
+
239
+ # @private
240
+ def failure_message_when_negated
241
+ "expected given block not to yield with no arguments, but #{negative_failure_reason}"
242
+ end
243
+
244
+ # @private
245
+ def supports_block_expectations?
246
+ true
247
+ end
248
+
249
+ private
250
+
251
+ def positive_failure_reason
252
+ return 'was not a block' unless @probe.has_block?
253
+ return 'did not yield' if @probe.num_yields.zero?
254
+ "yielded with arguments: #{description_of @probe.single_yield_args}"
255
+ end
256
+
257
+ def negative_failure_reason
258
+ return 'was not a block' unless @probe.has_block?
259
+ 'did'
260
+ end
261
+ end
262
+
263
+ # @api private
264
+ # Provides the implementation for `yield_with_args`.
265
+ # Not intended to be instantiated directly.
266
+ class YieldWithArgs < BaseMatcher
267
+ def initialize(*args)
268
+ @expected = args
269
+ end
270
+
271
+ # @private
272
+ def matches?(block)
273
+ @args_matched_when_yielded = true
274
+ @probe = YieldProbe.new(block) do
275
+ @actual = @probe.single_yield_args
276
+ @actual_formatted = actual_formatted
277
+ @args_matched_when_yielded &&= args_currently_match?
278
+ end
279
+ return false unless @probe.has_block?
280
+ @probe.probe
281
+ @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded
282
+ end
283
+
284
+ # @private
285
+ def does_not_match?(block)
286
+ !matches?(block) && @probe.has_block?
287
+ end
288
+
289
+ # @private
290
+ def failure_message
291
+ "expected given block to yield with arguments, but #{positive_failure_reason}"
292
+ end
293
+
294
+ # @private
295
+ def failure_message_when_negated
296
+ "expected given block not to yield with arguments, but #{negative_failure_reason}"
297
+ end
298
+
299
+ # @private
300
+ def description
301
+ desc = 'yield with args'
302
+ desc = "#{desc}(#{expected_arg_description})" unless @expected.empty?
303
+ desc
304
+ end
305
+
306
+ # @private
307
+ def supports_block_expectations?
308
+ true
309
+ end
310
+
311
+ private
312
+
313
+ def positive_failure_reason
314
+ return 'was not a block' unless @probe.has_block?
315
+ return 'did not yield' if @probe.num_yields.zero?
316
+ @positive_args_failure
317
+ end
318
+
319
+ def expected_arg_description
320
+ @expected.map { |e| description_of e }.join(', ')
321
+ end
322
+
323
+ def negative_failure_reason
324
+ if !@probe.has_block?
325
+ 'was not a block'
326
+ elsif @args_matched_when_yielded && !@expected.empty?
327
+ 'yielded with expected arguments' \
328
+ "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
329
+ "\n got: #{@actual_formatted}"
330
+ else
331
+ 'did'
332
+ end
333
+ end
334
+
335
+ def args_currently_match?
336
+ if @expected.empty? # expect {...}.to yield_with_args
337
+ @positive_args_failure = 'yielded with no arguments' if @actual.empty?
338
+ return !@actual.empty?
339
+ end
340
+
341
+ unless (match = all_args_match?)
342
+ @positive_args_failure = 'yielded with unexpected arguments' \
343
+ "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
344
+ "\n got: #{@actual_formatted}"
345
+ end
346
+
347
+ match
348
+ end
349
+
350
+ def all_args_match?
351
+ values_match?(@expected, @actual)
352
+ end
353
+ end
354
+
355
+ # @api private
356
+ # Provides the implementation for `yield_successive_args`.
357
+ # Not intended to be instantiated directly.
358
+ class YieldSuccessiveArgs < BaseMatcher
359
+ def initialize(*args)
360
+ @expected = args
361
+ end
362
+
363
+ # @private
364
+ def matches?(block)
365
+ @actual_formatted = []
366
+ @actual = []
367
+ args_matched_when_yielded = true
368
+ yield_count = 0
369
+
370
+ @probe = YieldProbe.probe(block) do |*arg_array|
371
+ arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array
372
+ @actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args)
373
+ @actual << arg_or_args
374
+ args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args)
375
+ yield_count += 1
376
+ end
377
+
378
+ return false unless @probe.has_block?
379
+ args_matched_when_yielded && yield_count == @expected.length
380
+ end
381
+
382
+ def does_not_match?(block)
383
+ !matches?(block) && @probe.has_block?
384
+ end
385
+
386
+ # @private
387
+ def failure_message
388
+ 'expected given block to yield successively with arguments, ' \
389
+ "but #{positive_failure_reason}"
390
+ end
391
+
392
+ # @private
393
+ def failure_message_when_negated
394
+ 'expected given block not to yield successively with arguments, ' \
395
+ "but #{negative_failure_reason}"
396
+ end
397
+
398
+ # @private
399
+ def description
400
+ "yield successive args(#{expected_arg_description})"
401
+ end
402
+
403
+ # @private
404
+ def supports_block_expectations?
405
+ true
406
+ end
407
+
408
+ private
409
+
410
+ def expected_arg_description
411
+ @expected.map { |e| description_of e }.join(', ')
412
+ end
413
+
414
+ def positive_failure_reason
415
+ return 'was not a block' unless @probe.has_block?
416
+
417
+ 'yielded with unexpected arguments' \
418
+ "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
419
+ "\n got: [#{@actual_formatted.join(", ")}]"
420
+ end
421
+
422
+ def negative_failure_reason
423
+ return 'was not a block' unless @probe.has_block?
424
+
425
+ 'yielded with expected arguments' \
426
+ "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
427
+ "\n got: [#{@actual_formatted.join(", ")}]"
428
+ end
429
+ end
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,171 @@
1
+ RSpec::Support.require_rspec_support "fuzzy_matcher"
2
+
3
+ module RSpec
4
+ module Matchers
5
+ # Mixin designed to support the composable matcher features
6
+ # of RSpec 3+. Mix it into your custom matcher classes to
7
+ # allow them to be used in a composable fashion.
8
+ #
9
+ # @api public
10
+ module Composable
11
+ # Creates a compound `and` expectation. The matcher will
12
+ # only pass if both sub-matchers pass.
13
+ # This can be chained together to form an arbitrarily long
14
+ # chain of matchers.
15
+ #
16
+ # @example
17
+ # expect(alphabet).to start_with("a").and end_with("z")
18
+ # expect(alphabet).to start_with("a") & end_with("z")
19
+ #
20
+ # @note The negative form (`expect(...).not_to matcher.and other`)
21
+ # is not supported at this time.
22
+ def and(matcher)
23
+ BuiltIn::Compound::And.new self, matcher
24
+ end
25
+ alias & and
26
+
27
+ # Creates a compound `or` expectation. The matcher will
28
+ # pass if either sub-matcher passes.
29
+ # This can be chained together to form an arbitrarily long
30
+ # chain of matchers.
31
+ #
32
+ # @example
33
+ # expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
34
+ # expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
35
+ #
36
+ # @note The negative form (`expect(...).not_to matcher.or other`)
37
+ # is not supported at this time.
38
+ def or(matcher)
39
+ BuiltIn::Compound::Or.new self, matcher
40
+ end
41
+ alias | or
42
+
43
+ # Delegates to `#matches?`. Allows matchers to be used in composable
44
+ # fashion and also supports using matchers in case statements.
45
+ def ===(value)
46
+ matches?(value)
47
+ end
48
+
49
+ private
50
+
51
+ # This provides a generic way to fuzzy-match an expected value against
52
+ # an actual value. It understands nested data structures (e.g. hashes
53
+ # and arrays) and is able to match against a matcher being used as
54
+ # the expected value or within the expected value at any level of
55
+ # nesting.
56
+ #
57
+ # Within a custom matcher you are encouraged to use this whenever your
58
+ # matcher needs to match two values, unless it needs more precise semantics.
59
+ # For example, the `eq` matcher _does not_ use this as it is meant to
60
+ # use `==` (and only `==`) for matching.
61
+ #
62
+ # @param expected [Object] what is expected
63
+ # @param actual [Object] the actual value
64
+ #
65
+ # @!visibility public
66
+ def values_match?(expected, actual)
67
+ expected = with_matchers_cloned(expected)
68
+ Support::FuzzyMatcher.values_match?(expected, actual)
69
+ end
70
+
71
+ # Returns the description of the given object in a way that is
72
+ # aware of composed matchers. If the object is a matcher with
73
+ # a `description` method, returns the description; otherwise
74
+ # returns `object.inspect`.
75
+ #
76
+ # You are encouraged to use this in your custom matcher's
77
+ # `description`, `failure_message` or
78
+ # `failure_message_when_negated` implementation if you are
79
+ # supporting matcher arguments.
80
+ #
81
+ # @!visibility public
82
+ def description_of(object)
83
+ RSpec::Support::ObjectFormatter.format(object)
84
+ end
85
+
86
+ # Transforms the given data structue (typically a hash or array)
87
+ # into a new data structure that, when `#inspect` is called on it,
88
+ # will provide descriptions of any contained matchers rather than
89
+ # the normal `#inspect` output.
90
+ #
91
+ # You are encouraged to use this in your custom matcher's
92
+ # `description`, `failure_message` or
93
+ # `failure_message_when_negated` implementation if you are
94
+ # supporting any arguments which may be a data structure
95
+ # containing matchers.
96
+ #
97
+ # @!visibility public
98
+ def surface_descriptions_in(item)
99
+ if Matchers.is_a_describable_matcher?(item)
100
+ DescribableItem.new(item)
101
+ elsif Hash === item
102
+ Hash[surface_descriptions_in(item.to_a)]
103
+ elsif Struct === item || unreadable_io?(item)
104
+ RSpec::Support::ObjectFormatter.format(item)
105
+ elsif should_enumerate?(item)
106
+ item.map { |subitem| surface_descriptions_in(subitem) }
107
+ else
108
+ item
109
+ end
110
+ end
111
+
112
+ # @private
113
+ # Historically, a single matcher instance was only checked
114
+ # against a single value. Given that the matcher was only
115
+ # used once, it's been common to memoize some intermediate
116
+ # calculation that is derived from the `actual` value in
117
+ # order to reuse that intermediate result in the failure
118
+ # message.
119
+ #
120
+ # This can cause a problem when using such a matcher as an
121
+ # argument to another matcher in a composed matcher expression,
122
+ # since the matcher instance may be checked against multiple
123
+ # values and produce invalid results due to the memoization.
124
+ #
125
+ # To deal with this, we clone any matchers in `expected` via
126
+ # this method when using `values_match?`, so that any memoization
127
+ # does not "leak" between checks.
128
+ def with_matchers_cloned(object)
129
+ if Matchers.is_a_matcher?(object)
130
+ object.clone
131
+ elsif Hash === object
132
+ Hash[with_matchers_cloned(object.to_a)]
133
+ elsif should_enumerate?(object)
134
+ object.map { |subobject| with_matchers_cloned(subobject) }
135
+ else
136
+ object
137
+ end
138
+ end
139
+
140
+ # @api private
141
+ # We should enumerate arrays as long as they are not recursive.
142
+ def should_enumerate?(item)
143
+ Array === item && item.none? { |subitem| subitem.equal?(item) }
144
+ end
145
+
146
+ # @api private
147
+ def unreadable_io?(object)
148
+ return false unless IO === object
149
+ object.each {} # STDOUT is enumerable but raises an error
150
+ false
151
+ rescue IOError
152
+ true
153
+ end
154
+ module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io?
155
+
156
+ # Wraps an item in order to surface its `description` via `inspect`.
157
+ # @api private
158
+ DescribableItem = Struct.new(:item) do
159
+ # Inspectable version of the item description
160
+ def inspect
161
+ "(#{item.description})"
162
+ end
163
+
164
+ # A pretty printed version of the item description.
165
+ def pretty_print(pp)
166
+ pp.text "(#{item.description})"
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end