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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +55 -4
  5. data/README.md +1 -1
  6. data/lib/rspec/expectations.rb +13 -1
  7. data/lib/rspec/expectations/configuration.rb +17 -0
  8. data/lib/rspec/expectations/expectation_target.rb +3 -9
  9. data/lib/rspec/expectations/fail_with.rb +1 -3
  10. data/lib/rspec/expectations/failure_aggregator.rb +194 -0
  11. data/lib/rspec/expectations/minitest_integration.rb +13 -0
  12. data/lib/rspec/expectations/version.rb +1 -1
  13. data/lib/rspec/matchers.rb +59 -5
  14. data/lib/rspec/matchers/built_in/base_matcher.rb +56 -7
  15. data/lib/rspec/matchers/built_in/be.rb +25 -15
  16. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  17. data/lib/rspec/matchers/built_in/be_within.rb +2 -2
  18. data/lib/rspec/matchers/built_in/contain_exactly.rb +12 -8
  19. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  20. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  21. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  22. data/lib/rspec/matchers/built_in/exist.rb +2 -2
  23. data/lib/rspec/matchers/built_in/has.rb +3 -1
  24. data/lib/rspec/matchers/built_in/have_attributes.rb +5 -4
  25. data/lib/rspec/matchers/built_in/include.rb +44 -19
  26. data/lib/rspec/matchers/built_in/match.rb +9 -1
  27. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  28. data/lib/rspec/matchers/built_in/output.rb +9 -2
  29. data/lib/rspec/matchers/built_in/raise_error.rb +64 -22
  30. data/lib/rspec/matchers/built_in/respond_to.rb +2 -3
  31. data/lib/rspec/matchers/built_in/satisfy.rb +7 -9
  32. data/lib/rspec/matchers/built_in/start_or_end_with.rb +3 -1
  33. data/lib/rspec/matchers/built_in/throw_symbol.rb +1 -1
  34. data/lib/rspec/matchers/built_in/yield.rb +7 -5
  35. data/lib/rspec/matchers/composable.rb +5 -4
  36. data/lib/rspec/matchers/dsl.rb +19 -6
  37. data/lib/rspec/matchers/english_phrasing.rb +42 -0
  38. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +2 -8
  39. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  40. data/lib/rspec/matchers/matcher_delegator.rb +2 -0
  41. metadata +9 -7
  42. metadata.gz.sig +0 -0
  43. 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.inspect}"
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 #{@actual.inspect} to #{description} but had attributes #{ formatted_values }"
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 #{@actual.inspect} not to #{description}" }
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
- improve_hash_formatting(@values.inspect)
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
- @actual = actual
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
- @actual = actual
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
- described_items = surface_descriptions_in(expected)
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
- improve_hash_formatting(super) + invalid_object_message
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
- improve_hash_formatting(super) + invalid_object_message
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
- true
45
+ !diff_would_wrongly_highlight_matched_item?
49
46
  end
50
47
 
51
48
  private
52
49
 
53
- def invalid_object_message
54
- return '' if actual.respond_to?(:include?)
55
- ", but it does not respond to `include?`"
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 perform_match(predicate, hash_subset_predicate)
59
- return false unless actual.respond_to?(:include?)
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
- expected.__send__(predicate) do |expected_item|
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.__send__(hash_subset_predicate) do |(key, value)|
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
- actual.match(expected) if actual.respond_to?(:match)
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.inspect}"
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
- fail_with_message("expected: #{operator} #{expected.inspect}\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
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
- fail_with_message("expected not: #{operator} #{expected.inspect}\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
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
- @actual.inspect
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=Exception, expected_message=nil, &block)
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 String, Regexp
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
- unless negative_expectation
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
- prevent_invalid_expectations
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 prevent_invalid_expectations
122
- what_to_raise = if expecting_specific_exception? && @expected_message
123
- "`expect { }.not_to raise_error(SpecificErrorClass, message)`"
124
- elsif expecting_specific_exception?
125
- "`expect { }.not_to raise_error(SpecificErrorClass)`"
126
- elsif @expected_message
127
- "`expect { }.not_to raise_error(message)`"
128
- end
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
- return unless what_to_raise
149
+ def warning_about_bare_error
150
+ @warn_about_bare_error && @block.nil?
151
+ end
131
152
 
132
- specific_class_error = ArgumentError.new("#{what_to_raise} is not valid, use `expect { }.not_to raise_error` (with no args) instead")
133
- raise specific_class_error
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
- description_of(@expected_error)
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.inspect}"
183
+ "#{@expected_error} with message matching #{description_of(@expected_message)}"
142
184
  else
143
- "#{@expected_error} with #{description_of @expected_message}"
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.inspect} with backtrace:",
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 #{@actual.inspect} to respond to #{@failing_method_names.map { |name| name.inspect }.join(', ')}#{with_arity}"
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
- # Ruby 1.9 returns the same thing for array.to_s as array.inspect, so just use array.inspect here
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
- def initialize(&block)
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 #{@actual} to satisfy block"
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 #{@actual} not to satisfy block"
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
- "#{name_to_sentence} #{surface_descriptions_in(expected).inspect}"
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