rspec-expectations 3.2.1 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +55 -4
- data/README.md +1 -1
- data/lib/rspec/expectations.rb +13 -1
- data/lib/rspec/expectations/configuration.rb +17 -0
- data/lib/rspec/expectations/expectation_target.rb +3 -9
- data/lib/rspec/expectations/fail_with.rb +1 -3
- data/lib/rspec/expectations/failure_aggregator.rb +194 -0
- data/lib/rspec/expectations/minitest_integration.rb +13 -0
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +59 -5
- data/lib/rspec/matchers/built_in/base_matcher.rb +56 -7
- data/lib/rspec/matchers/built_in/be.rb +25 -15
- data/lib/rspec/matchers/built_in/be_between.rb +1 -1
- data/lib/rspec/matchers/built_in/be_within.rb +2 -2
- data/lib/rspec/matchers/built_in/contain_exactly.rb +12 -8
- data/lib/rspec/matchers/built_in/eq.rb +3 -38
- data/lib/rspec/matchers/built_in/eql.rb +2 -2
- data/lib/rspec/matchers/built_in/equal.rb +3 -3
- data/lib/rspec/matchers/built_in/exist.rb +2 -2
- data/lib/rspec/matchers/built_in/has.rb +3 -1
- data/lib/rspec/matchers/built_in/have_attributes.rb +5 -4
- data/lib/rspec/matchers/built_in/include.rb +44 -19
- data/lib/rspec/matchers/built_in/match.rb +9 -1
- data/lib/rspec/matchers/built_in/operators.rb +14 -5
- data/lib/rspec/matchers/built_in/output.rb +9 -2
- data/lib/rspec/matchers/built_in/raise_error.rb +64 -22
- data/lib/rspec/matchers/built_in/respond_to.rb +2 -3
- data/lib/rspec/matchers/built_in/satisfy.rb +7 -9
- data/lib/rspec/matchers/built_in/start_or_end_with.rb +3 -1
- data/lib/rspec/matchers/built_in/throw_symbol.rb +1 -1
- data/lib/rspec/matchers/built_in/yield.rb +7 -5
- data/lib/rspec/matchers/composable.rb +5 -4
- data/lib/rspec/matchers/dsl.rb +19 -6
- data/lib/rspec/matchers/english_phrasing.rb +42 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +2 -8
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/matcher_delegator.rb +2 -0
- metadata +9 -7
- metadata.gz.sig +0 -0
- data/lib/rspec/matchers/pretty.rb +0 -77
@@ -42,7 +42,7 @@ module RSpec
|
|
42
42
|
# @return [String]
|
43
43
|
def description
|
44
44
|
described_items = surface_descriptions_in(expected)
|
45
|
-
improve_hash_formatting "have attributes #{described_items
|
45
|
+
improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}"
|
46
46
|
end
|
47
47
|
|
48
48
|
# @api private
|
@@ -55,14 +55,14 @@ module RSpec
|
|
55
55
|
# @return [String]
|
56
56
|
def failure_message
|
57
57
|
respond_to_failure_message_or do
|
58
|
-
"expected #{
|
58
|
+
"expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }"
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
# @api private
|
63
63
|
# @return [String]
|
64
64
|
def failure_message_when_negated
|
65
|
-
respond_to_failure_message_or { "expected #{
|
65
|
+
respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" }
|
66
66
|
end
|
67
67
|
|
68
68
|
private
|
@@ -105,7 +105,8 @@ module RSpec
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def formatted_values
|
108
|
-
|
108
|
+
values = RSpec::Support::ObjectFormatter.format(@values)
|
109
|
+
improve_hash_formatting(values)
|
109
110
|
end
|
110
111
|
end
|
111
112
|
end
|
@@ -12,62 +12,78 @@ module RSpec
|
|
12
12
|
# @api private
|
13
13
|
# @return [Boolean]
|
14
14
|
def matches?(actual)
|
15
|
-
|
16
|
-
perform_match(:all?, :all?)
|
15
|
+
perform_match(actual) { |v| v }
|
17
16
|
end
|
18
17
|
|
19
18
|
# @api private
|
20
19
|
# @return [Boolean]
|
21
20
|
def does_not_match?(actual)
|
22
|
-
|
23
|
-
perform_match(:none?, :any?)
|
21
|
+
perform_match(actual) { |v| !v }
|
24
22
|
end
|
25
23
|
|
26
24
|
# @api private
|
27
25
|
# @return [String]
|
28
26
|
def description
|
29
|
-
|
30
|
-
improve_hash_formatting "include#{to_sentence(described_items)}"
|
27
|
+
improve_hash_formatting("include#{readable_list_of(expected)}")
|
31
28
|
end
|
32
29
|
|
33
30
|
# @api private
|
34
31
|
# @return [String]
|
35
32
|
def failure_message
|
36
|
-
|
33
|
+
format_failure_message("to") { super }
|
37
34
|
end
|
38
35
|
|
39
36
|
# @api private
|
40
37
|
# @return [String]
|
41
38
|
def failure_message_when_negated
|
42
|
-
|
39
|
+
format_failure_message("not to") { super }
|
43
40
|
end
|
44
41
|
|
45
42
|
# @api private
|
46
43
|
# @return [Boolean]
|
47
44
|
def diffable?
|
48
|
-
|
45
|
+
!diff_would_wrongly_highlight_matched_item?
|
49
46
|
end
|
50
47
|
|
51
48
|
private
|
52
49
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
50
|
+
def format_failure_message(preposition)
|
51
|
+
if actual.respond_to?(:include?)
|
52
|
+
improve_hash_formatting("expected #{description_of @actual} #{preposition} include#{readable_list_of @divergent_items}")
|
53
|
+
else
|
54
|
+
improve_hash_formatting(yield) + ", but it does not respond to `include?`"
|
55
|
+
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
58
|
+
def readable_list_of(items)
|
59
|
+
described_items = surface_descriptions_in(items)
|
60
|
+
if described_items.all? { |item| item.is_a?(Hash) }
|
61
|
+
" #{described_items.inject(:merge).inspect}"
|
62
|
+
else
|
63
|
+
EnglishPhrasing.list(described_items)
|
64
|
+
end
|
65
|
+
end
|
60
66
|
|
61
|
-
|
67
|
+
def perform_match(actual, &block)
|
68
|
+
@actual = actual
|
69
|
+
@divergent_items = excluded_from_actual(&block)
|
70
|
+
actual.respond_to?(:include?) && @divergent_items.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def excluded_from_actual
|
74
|
+
return [] unless @actual.respond_to?(:include?)
|
75
|
+
|
76
|
+
expected.inject([]) do |memo, expected_item|
|
62
77
|
if comparing_hash_to_a_subset?(expected_item)
|
63
|
-
expected_item.
|
64
|
-
actual_hash_includes?(key, value)
|
78
|
+
expected_item.each do |(key, value)|
|
79
|
+
memo << { key => value } unless yield actual_hash_includes?(key, value)
|
65
80
|
end
|
66
81
|
elsif comparing_hash_keys?(expected_item)
|
67
|
-
actual_hash_has_key?(expected_item)
|
82
|
+
memo << expected_item unless yield actual_hash_has_key?(expected_item)
|
68
83
|
else
|
69
|
-
actual_collection_includes?(expected_item)
|
84
|
+
memo << expected_item unless yield actual_collection_includes?(expected_item)
|
70
85
|
end
|
86
|
+
memo
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
@@ -99,6 +115,15 @@ module RSpec
|
|
99
115
|
|
100
116
|
actual.any? { |value| values_match?(expected_item, value) }
|
101
117
|
end
|
118
|
+
|
119
|
+
def diff_would_wrongly_highlight_matched_item?
|
120
|
+
return false unless actual.is_a?(String) && expected.is_a?(Array)
|
121
|
+
|
122
|
+
lines = actual.split("\n")
|
123
|
+
expected.any? do |str|
|
124
|
+
actual.include?(str) && lines.none? { |line| line == str }
|
125
|
+
end
|
126
|
+
end
|
102
127
|
end
|
103
128
|
end
|
104
129
|
end
|
@@ -21,7 +21,15 @@ module RSpec
|
|
21
21
|
|
22
22
|
def match(expected, actual)
|
23
23
|
return true if values_match?(expected, actual)
|
24
|
-
|
24
|
+
return false unless can_safely_call_match?(expected, actual)
|
25
|
+
actual.match(expected)
|
26
|
+
end
|
27
|
+
|
28
|
+
def can_safely_call_match?(expected, actual)
|
29
|
+
return false unless actual.respond_to?(:match)
|
30
|
+
|
31
|
+
!(RSpec::Matchers.is_a_matcher?(expected) &&
|
32
|
+
(String === actual || Regexp === actual))
|
25
33
|
end
|
26
34
|
end
|
27
35
|
end
|
@@ -74,7 +74,7 @@ module RSpec
|
|
74
74
|
# @api private
|
75
75
|
# @return [String]
|
76
76
|
def description
|
77
|
-
"#{@operator} #{@expected
|
77
|
+
"#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}"
|
78
78
|
end
|
79
79
|
|
80
80
|
private
|
@@ -98,10 +98,15 @@ module RSpec
|
|
98
98
|
def __delegate_operator(actual, operator, expected)
|
99
99
|
if actual.__send__(operator, expected)
|
100
100
|
true
|
101
|
-
elsif ['==', '===', '=~'].include?(operator)
|
102
|
-
fail_with_message("expected: #{expected.inspect}\n got: #{actual.inspect} (using #{operator})")
|
103
101
|
else
|
104
|
-
|
102
|
+
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
|
103
|
+
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
|
104
|
+
|
105
|
+
if ['==', '===', '=~'].include?(operator)
|
106
|
+
fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})")
|
107
|
+
else
|
108
|
+
fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
|
109
|
+
end
|
105
110
|
end
|
106
111
|
end
|
107
112
|
end
|
@@ -111,7 +116,11 @@ module RSpec
|
|
111
116
|
class NegativeOperatorMatcher < OperatorMatcher
|
112
117
|
def __delegate_operator(actual, operator, expected)
|
113
118
|
return false unless actual.__send__(operator, expected)
|
114
|
-
|
119
|
+
|
120
|
+
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
|
121
|
+
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
|
122
|
+
|
123
|
+
fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
|
115
124
|
end
|
116
125
|
end
|
117
126
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
require 'tempfile'
|
3
2
|
|
4
3
|
module RSpec
|
5
4
|
module Matchers
|
@@ -114,7 +113,7 @@ module RSpec
|
|
114
113
|
|
115
114
|
def actual_output_description
|
116
115
|
return "nothing" unless captured?
|
117
|
-
|
116
|
+
actual_formatted
|
118
117
|
end
|
119
118
|
end
|
120
119
|
|
@@ -172,6 +171,14 @@ module RSpec
|
|
172
171
|
# @private
|
173
172
|
class CaptureStreamToTempfile < Struct.new(:name, :stream)
|
174
173
|
def capture(block)
|
174
|
+
# We delay loading tempfile until it is actually needed because
|
175
|
+
# we want to minimize stdlibs loaded so that users who use a
|
176
|
+
# portion of the stdlib can't have passing specs while forgetting
|
177
|
+
# to load it themselves. `CaptureStreamToTempfile` is rarely used
|
178
|
+
# and `tempfile` pulls in a bunch of things (delegate, tmpdir,
|
179
|
+
# thread, fileutils, etc), so it's worth delaying it until this point.
|
180
|
+
require 'tempfile'
|
181
|
+
|
175
182
|
original_stream = stream.clone
|
176
183
|
captured_stream = Tempfile.new(name)
|
177
184
|
|
@@ -8,11 +8,15 @@ module RSpec
|
|
8
8
|
class RaiseError
|
9
9
|
include Composable
|
10
10
|
|
11
|
-
def initialize(expected_error_or_message=
|
11
|
+
def initialize(expected_error_or_message=nil, expected_message=nil, &block)
|
12
12
|
@block = block
|
13
13
|
@actual_error = nil
|
14
|
+
@warn_about_bare_error = warn_about_potential_false_positives? && expected_error_or_message.nil?
|
15
|
+
|
14
16
|
case expected_error_or_message
|
15
|
-
when
|
17
|
+
when nil
|
18
|
+
@expected_error, @expected_message = Exception, expected_message
|
19
|
+
when String
|
16
20
|
@expected_error, @expected_message = Exception, expected_error_or_message
|
17
21
|
else
|
18
22
|
@expected_error, @expected_message = expected_error_or_message, expected_message
|
@@ -23,6 +27,7 @@ module RSpec
|
|
23
27
|
# Specifies the expected error message.
|
24
28
|
def with_message(expected_message)
|
25
29
|
raise_message_already_set if @expected_message
|
30
|
+
@warn_about_bare_error = false
|
26
31
|
@expected_message = expected_message
|
27
32
|
self
|
28
33
|
end
|
@@ -37,20 +42,20 @@ module RSpec
|
|
37
42
|
@eval_block = false
|
38
43
|
@eval_block_passed = false
|
39
44
|
|
45
|
+
warn_about_bare_error if warning_about_bare_error && !negative_expectation
|
40
46
|
return false unless Proc === given_proc
|
41
47
|
|
42
48
|
begin
|
43
49
|
given_proc.call
|
44
50
|
rescue Exception => @actual_error
|
45
|
-
if values_match?(@expected_error, @actual_error)
|
51
|
+
if values_match?(@expected_error, @actual_error) ||
|
52
|
+
values_match?(@expected_error, @actual_error.message)
|
46
53
|
@raised_expected_error = true
|
47
54
|
@with_expected_message = verify_message
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
51
|
-
|
52
|
-
eval_block if @raised_expected_error && @with_expected_message && @block
|
53
|
-
end
|
58
|
+
eval_block if !negative_expectation && ready_to_eval_block?
|
54
59
|
|
55
60
|
expectation_matched?
|
56
61
|
end
|
@@ -58,7 +63,7 @@ module RSpec
|
|
58
63
|
|
59
64
|
# @private
|
60
65
|
def does_not_match?(given_proc)
|
61
|
-
|
66
|
+
warn_for_false_positives
|
62
67
|
!matches?(given_proc, :negative_expectation) && Proc === given_proc
|
63
68
|
end
|
64
69
|
|
@@ -103,6 +108,10 @@ module RSpec
|
|
103
108
|
@eval_block ? @eval_block_passed : true
|
104
109
|
end
|
105
110
|
|
111
|
+
def ready_to_eval_block?
|
112
|
+
@raised_expected_error && @with_expected_message && @block
|
113
|
+
end
|
114
|
+
|
106
115
|
def eval_block
|
107
116
|
@eval_block = true
|
108
117
|
begin
|
@@ -118,29 +127,62 @@ module RSpec
|
|
118
127
|
values_match?(@expected_message, @actual_error.message.to_s)
|
119
128
|
end
|
120
129
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
130
|
+
def warn_for_false_positives
|
131
|
+
return unless warn_about_potential_false_positives?
|
132
|
+
expression = if expecting_specific_exception? && @expected_message
|
133
|
+
"`expect { }.not_to raise_error(SpecificErrorClass, message)`"
|
134
|
+
elsif expecting_specific_exception?
|
135
|
+
"`expect { }.not_to raise_error(SpecificErrorClass)`"
|
136
|
+
elsif @expected_message
|
137
|
+
"`expect { }.not_to raise_error(message)`"
|
138
|
+
end
|
139
|
+
|
140
|
+
return unless expression
|
141
|
+
|
142
|
+
warn_about_negative_false_positive expression
|
143
|
+
end
|
144
|
+
|
145
|
+
def warn_about_potential_false_positives?
|
146
|
+
RSpec::Expectations.configuration.warn_about_potential_false_positives?
|
147
|
+
end
|
129
148
|
|
130
|
-
|
149
|
+
def warning_about_bare_error
|
150
|
+
@warn_about_bare_error && @block.nil?
|
151
|
+
end
|
131
152
|
|
132
|
-
|
133
|
-
|
153
|
+
def warn_about_bare_error
|
154
|
+
RSpec.warning("Using the `raise_error` matcher without providing a specific " \
|
155
|
+
"error or message risks false positives, since `raise_error` " \
|
156
|
+
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
|
157
|
+
"`ArgumentError`, potentially allowing the expectation to pass " \
|
158
|
+
"without even executing the method you are intending to call. " \
|
159
|
+
"Instead consider providing a specific error class or message. " \
|
160
|
+
"This message can be supressed by setting: " \
|
161
|
+
"`RSpec::Expectations.configuration.warn_about_potential_false_positives = false`")
|
162
|
+
end
|
163
|
+
|
164
|
+
def warn_about_negative_false_positive(expression)
|
165
|
+
RSpec.warning("Using #{expression} risks false positives, since literally " \
|
166
|
+
"any other error would cause the expectation to pass, " \
|
167
|
+
"including those raised by Ruby (e.g. NoMethodError, NameError " \
|
168
|
+
"and ArgumentError), meaning the code you are intending to test " \
|
169
|
+
"may not even get reached. Instead consider using " \
|
170
|
+
"`expect {}.not_to raise_error`. This message can be supressed by setting: " \
|
171
|
+
"`RSpec::Expectations.configuration.warn_about_potential_false_positives = false`")
|
134
172
|
end
|
135
173
|
|
136
174
|
def expected_error
|
137
175
|
case @expected_message
|
138
176
|
when nil
|
139
|
-
|
177
|
+
if RSpec::Support.is_a_matcher?(@expected_error)
|
178
|
+
"Exception with #{description_of(@expected_error)}"
|
179
|
+
else
|
180
|
+
description_of(@expected_error)
|
181
|
+
end
|
140
182
|
when Regexp
|
141
|
-
"#{@expected_error} with message matching #{@expected_message
|
183
|
+
"#{@expected_error} with message matching #{description_of(@expected_message)}"
|
142
184
|
else
|
143
|
-
"#{@expected_error} with #{description_of
|
185
|
+
"#{@expected_error} with #{description_of(@expected_message)}"
|
144
186
|
end
|
145
187
|
end
|
146
188
|
|
@@ -155,7 +197,7 @@ module RSpec
|
|
155
197
|
|
156
198
|
backtrace = format_backtrace(@actual_error.backtrace)
|
157
199
|
[
|
158
|
-
", got #{@actual_error
|
200
|
+
", got #{description_of(@actual_error)} with backtrace:",
|
159
201
|
*backtrace
|
160
202
|
].join("\n # ")
|
161
203
|
end
|
@@ -45,7 +45,7 @@ module RSpec
|
|
45
45
|
# @api private
|
46
46
|
# @return [String]
|
47
47
|
def failure_message
|
48
|
-
"expected #{
|
48
|
+
"expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}"
|
49
49
|
end
|
50
50
|
|
51
51
|
# @api private
|
@@ -82,8 +82,7 @@ module RSpec
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def pp_names
|
85
|
-
|
86
|
-
@names.length == 1 ? "##{@names.first}" : @names.inspect
|
85
|
+
@names.length == 1 ? "##{@names.first}" : description_of(@names)
|
87
86
|
end
|
88
87
|
end
|
89
88
|
end
|
@@ -5,7 +5,11 @@ module RSpec
|
|
5
5
|
# Provides the implementation for `satisfy`.
|
6
6
|
# Not intended to be instantiated directly.
|
7
7
|
class Satisfy < BaseMatcher
|
8
|
-
|
8
|
+
# @private
|
9
|
+
attr_reader :description
|
10
|
+
|
11
|
+
def initialize(description="satisfy block", &block)
|
12
|
+
@description = description
|
9
13
|
@block = block
|
10
14
|
end
|
11
15
|
|
@@ -19,19 +23,13 @@ module RSpec
|
|
19
23
|
# @api private
|
20
24
|
# @return [String]
|
21
25
|
def failure_message
|
22
|
-
"expected #{
|
26
|
+
"expected #{actual_formatted} to #{description}"
|
23
27
|
end
|
24
28
|
|
25
29
|
# @api private
|
26
30
|
# @return [String]
|
27
31
|
def failure_message_when_negated
|
28
|
-
"expected #{
|
29
|
-
end
|
30
|
-
|
31
|
-
# @api private
|
32
|
-
# @return [String]
|
33
|
-
def description
|
34
|
-
"satisfy block"
|
32
|
+
"expected #{actual_formatted} not to #{description}"
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
@@ -26,7 +26,9 @@ module RSpec
|
|
26
26
|
# @return [String]
|
27
27
|
def description
|
28
28
|
return super unless Hash === expected
|
29
|
-
|
29
|
+
english_name = EnglishPhrasing.split_words(self.class.matcher_name)
|
30
|
+
description_of_expected = surface_descriptions_in(expected).inspect
|
31
|
+
"#{english_name} #{description_of_expected}"
|
30
32
|
end
|
31
33
|
|
32
34
|
private
|