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
@@ -77,7 +77,11 @@ module RSpec
77
77
  end
78
78
 
79
79
  def predicates
80
- @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
80
+ @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) }
81
+ end
82
+
83
+ def deprecated(predicate, actual)
84
+ predicate == :exists? && File == actual
81
85
  end
82
86
  end
83
87
  end
@@ -77,7 +77,7 @@ module RSpec
77
77
  end
78
78
 
79
79
  def method_description
80
- @method_name.to_s.gsub('_', ' ')
80
+ @method_name.to_s.tr('_', ' ')
81
81
  end
82
82
 
83
83
  def args_description
@@ -15,12 +15,14 @@ module RSpec
15
15
  # @api private
16
16
  # @return [Boolean]
17
17
  def matches?(actual)
18
+ actual = actual.to_hash if convert_to_hash?(actual)
18
19
  perform_match(actual) { |v| v }
19
20
  end
20
21
 
21
22
  # @api private
22
23
  # @return [Boolean]
23
24
  def does_not_match?(actual)
25
+ actual = actual.to_hash if convert_to_hash?(actual)
24
26
  perform_match(actual) { |v| !v }
25
27
  end
26
28
 
@@ -137,6 +139,10 @@ module RSpec
137
139
  actual.include?(str) && lines.none? { |line| line == str }
138
140
  end
139
141
  end
142
+
143
+ def convert_to_hash?(obj)
144
+ !obj.respond_to?(:include?) && obj.respond_to?(:to_hash)
145
+ end
140
146
  end
141
147
  end
142
148
  end
@@ -172,7 +172,7 @@ module RSpec
172
172
  "including those raised by Ruby (e.g. NoMethodError, NameError " \
173
173
  "and ArgumentError), meaning the code you are intending to test " \
174
174
  "may not even get reached. Instead consider using " \
175
- "`expect {}.not_to raise_error` or `expect { }.to raise_error" \
175
+ "`expect { }.not_to raise_error` or `expect { }.to raise_error" \
176
176
  "(DifferentSpecificErrorClass)`. This message can be suppressed by " \
177
177
  "setting: `RSpec::Expectations.configuration.on_potential_false" \
178
178
  "_positives = :nothing`")
@@ -125,13 +125,22 @@ module RSpec
125
125
 
126
126
  return true if expectation.empty?
127
127
 
128
- signature = Support::MethodSignature.new(Support.method_handle_for(actual, name))
128
+ Support::StrictSignatureVerifier.new(method_signature_for(actual, name)).
129
+ with_expectation(expectation).valid?
130
+ end
131
+
132
+ def method_signature_for(actual, name)
133
+ method_handle = Support.method_handle_for(actual, name)
129
134
 
130
- Support::StrictSignatureVerifier.new(signature).with_expectation(expectation).valid?
135
+ if name == :new && method_handle.owner === ::Class && ::Class === actual
136
+ Support::MethodSignature.new(actual.instance_method(:initialize))
137
+ else
138
+ Support::MethodSignature.new(method_handle)
139
+ end
131
140
  end
132
141
 
133
142
  def with_arity
134
- str = ''
143
+ str = ''.dup
135
144
  str << " with #{with_arity_string}" if @expected_arity
136
145
  str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0
137
146
  str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments
@@ -151,7 +160,7 @@ module RSpec
151
160
  @expected_keywords.map(&:inspect).join(' and ')
152
161
  else
153
162
  "#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}"
154
- end
163
+ end
155
164
 
156
165
  "keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}"
157
166
  end
@@ -5,10 +5,7 @@ module RSpec
5
5
  # Provides the implementation for `satisfy`.
6
6
  # Not intended to be instantiated directly.
7
7
  class Satisfy < BaseMatcher
8
- # @private
9
- attr_reader :description
10
-
11
- def initialize(description="satisfy block", &block)
8
+ def initialize(description=nil, &block)
12
9
  @description = description
13
10
  @block = block
14
11
  end
@@ -20,6 +17,11 @@ module RSpec
20
17
  @block.call(actual)
21
18
  end
22
19
 
20
+ # @private
21
+ def description
22
+ @description ||= "satisfy #{block_representation}"
23
+ end
24
+
23
25
  # @api private
24
26
  # @return [String]
25
27
  def failure_message
@@ -31,6 +33,27 @@ module RSpec
31
33
  def failure_message_when_negated
32
34
  "expected #{actual_formatted} not to #{description}"
33
35
  end
36
+
37
+ private
38
+
39
+ if RSpec::Support::RubyFeatures.ripper_supported?
40
+ def block_representation
41
+ if (block_snippet = extract_block_snippet)
42
+ "expression `#{block_snippet}`"
43
+ else
44
+ 'block'
45
+ end
46
+ end
47
+
48
+ def extract_block_snippet
49
+ return nil unless @block
50
+ Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
51
+ end
52
+ else
53
+ def block_representation
54
+ 'block'
55
+ end
56
+ end
34
57
  end
35
58
  end
36
59
  end
@@ -8,19 +8,17 @@ module RSpec
8
8
  # yield matchers is used. Provides information about
9
9
  # the yield behavior of the object-under-test.
10
10
  class YieldProbe
11
- def self.probe(block)
12
- probe = new(block)
11
+ def self.probe(block, &callback)
12
+ probe = new(block, &callback)
13
13
  return probe unless probe.has_block?
14
- probe.assert_valid_expect_block!
15
- block.call(probe)
16
- probe.assert_used!
17
- probe
14
+ probe.probe
18
15
  end
19
16
 
20
17
  attr_accessor :num_yields, :yielded_args
21
18
 
22
- def initialize(block)
19
+ def initialize(block, &callback)
23
20
  @block = block
21
+ @callback = callback || Proc.new {}
24
22
  @used = false
25
23
  self.num_yields = 0
26
24
  self.yielded_args = []
@@ -30,13 +28,22 @@ module RSpec
30
28
  Proc === @block
31
29
  end
32
30
 
31
+ def probe
32
+ assert_valid_expect_block!
33
+ @block.call(self)
34
+ assert_used!
35
+ self
36
+ end
37
+
33
38
  def to_proc
34
39
  @used = true
35
40
 
36
41
  probe = self
42
+ callback = @callback
37
43
  Proc.new do |*args|
38
44
  probe.num_yields += 1
39
45
  probe.yielded_args << args
46
+ callback.call(*args)
40
47
  nil # to indicate the block does not return a meaningful value
41
48
  end
42
49
  end
@@ -56,12 +63,6 @@ module RSpec
56
63
  end
57
64
  end
58
65
 
59
- def successive_yield_args
60
- yielded_args.map do |arg_array|
61
- arg_array.size == 1 ? arg_array.first : arg_array
62
- end
63
- end
64
-
65
66
  def assert_used!
66
67
  return if @used
67
68
  raise 'You must pass the argument yielded to your expect block on ' \
@@ -269,10 +270,15 @@ module RSpec
269
270
 
270
271
  # @private
271
272
  def matches?(block)
272
- @probe = YieldProbe.probe(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
273
279
  return false unless @probe.has_block?
274
- @actual = @probe.single_yield_args
275
- @probe.yielded_once?(:yield_with_args) && args_match?
280
+ @probe.probe
281
+ @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded
276
282
  end
277
283
 
278
284
  # @private
@@ -293,7 +299,7 @@ module RSpec
293
299
  # @private
294
300
  def description
295
301
  desc = 'yield with args'
296
- desc << "(#{expected_arg_description})" unless @expected.empty?
302
+ desc = "#{desc}(#{expected_arg_description})" unless @expected.empty?
297
303
  desc
298
304
  end
299
305
 
@@ -317,16 +323,16 @@ module RSpec
317
323
  def negative_failure_reason
318
324
  if !@probe.has_block?
319
325
  'was not a block'
320
- elsif all_args_match?
326
+ elsif @args_matched_when_yielded && !@expected.empty?
321
327
  'yielded with expected arguments' \
322
328
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
323
- "\n got: #{actual_formatted}"
329
+ "\n got: #{@actual_formatted}"
324
330
  else
325
331
  'did'
326
332
  end
327
333
  end
328
334
 
329
- def args_match?
335
+ def args_currently_match?
330
336
  if @expected.empty? # expect {...}.to yield_with_args
331
337
  @positive_args_failure = 'yielded with no arguments' if @actual.empty?
332
338
  return !@actual.empty?
@@ -335,7 +341,7 @@ module RSpec
335
341
  unless (match = all_args_match?)
336
342
  @positive_args_failure = 'yielded with unexpected arguments' \
337
343
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
338
- "\n got: #{actual_formatted}"
344
+ "\n got: #{@actual_formatted}"
339
345
  end
340
346
 
341
347
  match
@@ -356,10 +362,21 @@ module RSpec
356
362
 
357
363
  # @private
358
364
  def matches?(block)
359
- @probe = YieldProbe.probe(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
+
360
378
  return false unless @probe.has_block?
361
- @actual = @probe.successive_yield_args
362
- args_match?
379
+ args_matched_when_yielded && yield_count == @expected.length
363
380
  end
364
381
 
365
382
  def does_not_match?(block)
@@ -390,10 +407,6 @@ module RSpec
390
407
 
391
408
  private
392
409
 
393
- def args_match?
394
- values_match?(@expected, @actual)
395
- end
396
-
397
410
  def expected_arg_description
398
411
  @expected.map { |e| description_of e }.join(', ')
399
412
  end
@@ -403,7 +416,7 @@ module RSpec
403
416
 
404
417
  'yielded with unexpected arguments' \
405
418
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
406
- "\n got: #{actual_formatted}"
419
+ "\n got: [#{@actual_formatted.join(", ")}]"
407
420
  end
408
421
 
409
422
  def negative_failure_reason
@@ -411,7 +424,7 @@ module RSpec
411
424
 
412
425
  'yielded with expected arguments' \
413
426
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
414
- "\n got: #{actual_formatted}"
427
+ "\n got: [#{@actual_formatted.join(", ")}]"
415
428
  end
416
429
  end
417
430
  end
@@ -130,8 +130,6 @@ module RSpec
130
130
  object.clone
131
131
  elsif Hash === object
132
132
  Hash[with_matchers_cloned(object.to_a)]
133
- elsif Struct === object || unreadable_io?(object)
134
- object
135
133
  elsif should_enumerate?(object)
136
134
  object.map { |subobject| with_matchers_cloned(subobject) }
137
135
  else
@@ -139,24 +137,10 @@ module RSpec
139
137
  end
140
138
  end
141
139
 
142
- if String.ancestors.include?(Enumerable) # 1.8.7
143
- # :nocov:
144
- # Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
145
- # nested enumerable: since ruby lacks a character class, it yields
146
- # 1-character strings, which are themselves enumerable, composed of a
147
- # a single 1-character string, which is an enumerable, etc.
148
- #
149
- # @api private
150
- def should_enumerate?(item)
151
- return false if String === item
152
- Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
153
- end
154
- # :nocov:
155
- else
156
- # @api private
157
- def should_enumerate?(item)
158
- Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
159
- end
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) }
160
144
  end
161
145
 
162
146
  # @api private
@@ -172,10 +156,12 @@ module RSpec
172
156
  # Wraps an item in order to surface its `description` via `inspect`.
173
157
  # @api private
174
158
  DescribableItem = Struct.new(:item) do
159
+ # Inspectable version of the item description
175
160
  def inspect
176
161
  "(#{item.description})"
177
162
  end
178
163
 
164
+ # A pretty printed version of the item description.
179
165
  def pretty_print(pp)
180
166
  pp.text "(#{item.description})"
181
167
  end
@@ -2,7 +2,70 @@ module RSpec
2
2
  module Matchers
3
3
  # Defines the custom matcher DSL.
4
4
  module DSL
5
+ # Defines a matcher alias. The returned matcher's `description` will be overriden
6
+ # to reflect the phrasing of the new name, which will be used in failure messages
7
+ # when passed as an argument to another matcher in a composed matcher expression.
8
+ #
9
+ # @example
10
+ # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
11
+ # sum_to(3).description # => "sum to 3"
12
+ # a_list_that_sums_to(3).description # => "a list that sums to 3"
13
+ #
14
+ # @example
15
+ # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
16
+ # description.sub("be sorted by", "a list sorted by")
17
+ # end
18
+ #
19
+ # be_sorted_by(:age).description # => "be sorted by age"
20
+ # a_list_sorted_by(:age).description # => "a list sorted by age"
21
+ #
22
+ # @param new_name [Symbol] the new name for the matcher
23
+ # @param old_name [Symbol] the original name for the matcher
24
+ # @param options [Hash] options for the aliased matcher
25
+ # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
26
+ # @yield [String] optional block that, when given, is used to define the overriden
27
+ # logic. The yielded arg is the original description or failure message. If no
28
+ # block is provided, a default override is used based on the old and new names.
29
+ # @see RSpec::Matchers
30
+ def alias_matcher(new_name, old_name, options={}, &description_override)
31
+ description_override ||= lambda do |old_desc|
32
+ old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
33
+ end
34
+ klass = options.fetch(:klass) { AliasedMatcher }
35
+
36
+ define_method(new_name) do |*args, &block|
37
+ matcher = __send__(old_name, *args, &block)
38
+ matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=)
39
+ klass.new(matcher, description_override)
40
+ end
41
+ end
42
+
43
+ # Defines a negated matcher. The returned matcher's `description` and `failure_message`
44
+ # will be overriden to reflect the phrasing of the new name, and the match logic will
45
+ # be based on the original matcher but negated.
46
+ #
47
+ # @example
48
+ # RSpec::Matchers.define_negated_matcher :exclude, :include
49
+ # include(1, 2).description # => "include 1 and 2"
50
+ # exclude(1, 2).description # => "exclude 1 and 2"
51
+ #
52
+ # @param negated_name [Symbol] the name for the negated matcher
53
+ # @param base_name [Symbol] the name of the original matcher that will be negated
54
+ # @yield [String] optional block that, when given, is used to define the overriden
55
+ # logic. The yielded arg is the original description or failure message. If no
56
+ # block is provided, a default override is used based on the old and new names.
57
+ # @see RSpec::Matchers
58
+ def define_negated_matcher(negated_name, base_name, &description_override)
59
+ alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
60
+ end
61
+
5
62
  # Defines a custom matcher.
63
+ #
64
+ # @param name [Symbol] the name for the matcher
65
+ # @yield [Object] block that is used to define the matcher.
66
+ # The block is evaluated in the context of your custom matcher class.
67
+ # When args are passed to your matcher, they will be yielded here,
68
+ # usually representing the expected value(s).
6
69
  # @see RSpec::Matchers
7
70
  def define(name, &declarations)
8
71
  warn_about_block_args(name, declarations)
@@ -84,8 +147,14 @@ module RSpec
84
147
  # is rarely necessary, but can be helpful, for example, when specifying
85
148
  # asynchronous processes that require different timeouts.
86
149
  #
150
+ # By default the match block will swallow expectation errors (e.g.
151
+ # caused by using an expectation such as `expect(1).to eq 2`), if you
152
+ # with to allow these to bubble up, pass in the option
153
+ # `:notify_expectation_failures => true`.
154
+ #
155
+ # @param [Hash] options for defining the behavior of the match block.
87
156
  # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
88
- def match_when_negated(&match_block)
157
+ def match_when_negated(options={}, &match_block)
89
158
  define_user_override(:does_not_match?, match_block) do |actual|
90
159
  begin
91
160
  @actual = actual
@@ -93,6 +162,7 @@ module RSpec
93
162
  super(*actual_arg_for(match_block))
94
163
  end
95
164
  rescue RSpec::Expectations::ExpectationNotMetError
165
+ raise if options[:notify_expectation_failures]
96
166
  false
97
167
  end
98
168
  end
@@ -266,7 +336,7 @@ module RSpec
266
336
  #
267
337
  # This compiles the user block into an actual method, allowing
268
338
  # them to use normal method constructs like `return`
269
- # (e.g. for a early guard statement), while allowing us to define
339
+ # (e.g. for an early guard statement), while allowing us to define
270
340
  # an override that can provide the wrapped handling
271
341
  # (e.g. assigning `@actual`, rescueing errors, etc) and
272
342
  # can `super` to the user's definition.
@@ -462,5 +532,3 @@ module RSpec
462
532
  end
463
533
  end
464
534
  end
465
-
466
- RSpec::Matchers.extend RSpec::Matchers::DSL