1
- require "rspec/rails/matchers/base_matcher"
2
-
3
1
module RSpec
4
2
module Rails
5
3
module Matchers
@@ -26,25 +24,20 @@ def report(error, **attrs)
26
24
# Matcher class for `have_reported_error`. Should not be instantiated directly.
27
25
#
28
26
# Provides a way to test that an error was reported to Rails.error.
29
- # This matcher follows the same patterns as RSpec's built-in `raise_error` matcher.
30
27
#
31
28
# @api private
32
29
# @see RSpec::Rails::Matchers#have_reported_error
33
30
class HaveReportedError < RSpec ::Rails ::Matchers ::BaseMatcher
34
- # Initialize the matcher following raise_error patterns
35
- #
36
- # Uses UndefinedValue as default to distinguish between no argument
37
- # passed vs explicitly passed nil (same as raise_error matcher).
31
+ # Uses UndefinedValue as default to distinguish between no argument
32
+ # passed vs explicitly passed nil.
38
33
#
39
- # @param expected_error_or_message [Class, String, Regexp, nil]
34
+ # @param expected_error_or_message [Class, String, Regexp, nil]
40
35
# Error class, message string, or message pattern
41
- # @param expected_message [String, Regexp, nil]
36
+ # @param expected_message [String, Regexp, nil]
42
37
# Expected message when first param is a class
43
38
def initialize ( expected_error_or_message = UndefinedValue , expected_message = nil )
44
- @actual_error = nil
45
39
@attributes = { }
46
- @error_subscriber = nil
47
-
40
+
48
41
case expected_error_or_message
49
42
when nil , UndefinedValue
50
43
@expected_error = nil
@@ -67,7 +60,6 @@ def and(_)
67
60
raise ArgumentError , "Chaining is not supported"
68
61
end
69
62
70
- # Check if the block reports an error matching our expectations
71
63
def matches? ( block )
72
64
if block . nil?
73
65
raise ArgumentError , "this matcher doesn't work with value expectations"
@@ -122,6 +114,17 @@ def failure_message
122
114
end
123
115
elsif @error_subscriber . events . empty?
124
116
return 'Expected the block to report an error, but none was reported.'
117
+ elsif actual_error . nil?
118
+ reported_errors = @error_subscriber . events . map { |event | "#{ event . error . class } : '#{ event . error . message } '" } . join ( ', ' )
119
+ if @expected_error && @expected_message
120
+ return "Expected error to be an instance of #{ @expected_error } with message '#{ @expected_message } ', but got: #{ reported_errors } "
121
+ elsif @expected_error
122
+ return "Expected error to be an instance of #{ @expected_error } , but got: #{ reported_errors } "
123
+ elsif @expected_message . is_a? ( Regexp )
124
+ return "Expected error message to match #{ @expected_message } , but got: #{ reported_errors } "
125
+ elsif @expected_message . is_a? ( String )
126
+ return "Expected error message to be '#{ @expected_message } ', but got: #{ reported_errors } "
127
+ end
125
128
else
126
129
if @expected_error && !actual_error . is_a? ( @expected_error )
127
130
return "Expected error to be an instance of #{ @expected_error } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
@@ -148,44 +151,52 @@ def failure_message_when_negated
148
151
149
152
private
150
153
151
- # Check if the reported error matches our class and message expectations
152
154
def error_matches_expectation?
153
- return false if @error_subscriber . events . empty?
154
- return true if @expected_error . nil? && @expected_message . nil?
155
+ return true if @expected_error . nil? && @expected_message . nil? && @error_subscriber . events . count . positive?
155
156
156
- error_class_matches? && error_message_matches?
157
+ @error_subscriber . events . any? do |event |
158
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
159
+ end
157
160
end
158
161
159
- # Check if the actual error class matches the expected error class
160
- def error_class_matches?
161
- @expected_error . nil? || actual_error . is_a? ( @expected_error )
162
+ def error_class_matches? ( error )
163
+ @expected_error . nil? || error . is_a? ( @expected_error )
162
164
end
163
165
164
- # Check if the actual error message matches the expected message pattern
165
- def error_message_matches?
166
+ # Check if the given error message matches the expected message pattern
167
+ def error_message_matches? ( error )
166
168
return true if @expected_message . nil?
167
-
169
+
168
170
case @expected_message
169
171
when Regexp
170
- actual_error . message &.match ( @expected_message )
172
+ error . message &.match ( @expected_message )
171
173
when String
172
- actual_error . message == @expected_message
174
+ error . message == @expected_message
173
175
else
174
176
false
175
177
end
176
178
end
177
179
178
180
def attributes_match_if_specified?
179
181
return true if @attributes . empty?
180
- return false if @error_subscriber . events . empty?
182
+ return false unless matching_event
181
183
182
- event_context = @error_subscriber . events . last . attributes [ :context ]
184
+ event_context = matching_event . attributes [ :context ]
183
185
attributes_match? ( event_context )
184
186
end
185
187
186
- # Get the actual error that was reported (cached)
187
188
def actual_error
188
- @actual_error ||= ( @error_subscriber . events . empty? ? nil : @error_subscriber . events . last . error )
189
+ @actual_error ||= matching_event &.error
190
+ end
191
+
192
+ def matching_event
193
+ @matching_event ||= find_matching_event
194
+ end
195
+
196
+ def find_matching_event
197
+ @error_subscriber . events . find do |event |
198
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
199
+ end
189
200
end
190
201
191
202
def attributes_match? ( actual )
0 commit comments