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
@@ -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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
@
|
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
|
-
@
|
275
|
-
@probe.yielded_once?(:yield_with_args) &&
|
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
|
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
|
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
|
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
|
-
@
|
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
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
data/lib/rspec/matchers/dsl.rb
CHANGED
@@ -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
|
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
|