@@ -26,25 +26,22 @@ def report(error, **attrs)
26
26
# Matcher class for `have_reported_error`. Should not be instantiated directly.
27
27
#
28
28
# 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
29
#
31
30
# @api private
32
31
# @see RSpec::Rails::Matchers#have_reported_error
33
32
class HaveReportedError < RSpec ::Rails ::Matchers ::BaseMatcher
34
- # Initialize the matcher following raise_error patterns
33
+ # Uses UndefinedValue as default to distinguish between no argument
34
+ # passed vs explicitly passed nil.
35
35
#
36
- # Uses UndefinedValue as default to distinguish between no argument
37
- # passed vs explicitly passed nil (same as raise_error matcher).
38
- #
39
- # @param expected_error_or_message [Class, String, Regexp, nil]
36
+ # @param expected_error_or_message [Class, String, Regexp, nil]
40
37
# Error class, message string, or message pattern
41
- # @param expected_message [String, Regexp, nil]
38
+ # @param expected_message [String, Regexp, nil]
42
39
# Expected message when first param is a class
43
40
def initialize ( expected_error_or_message = UndefinedValue , expected_message = nil )
44
41
@actual_error = nil
45
42
@attributes = { }
46
43
@error_subscriber = nil
47
-
44
+
48
45
case expected_error_or_message
49
46
when nil , UndefinedValue
50
47
@expected_error = nil
@@ -67,7 +64,6 @@ def and(_)
67
64
raise ArgumentError , "Chaining is not supported"
68
65
end
69
66
70
- # Check if the block reports an error matching our expectations
71
67
def matches? ( block )
72
68
if block . nil?
73
69
raise ArgumentError , "this matcher doesn't work with value expectations"
@@ -89,6 +85,8 @@ def matches?(block)
89
85
def supports_block_expectations?
90
86
true
91
87
end
88
+
89
+
92
90
93
91
def description
94
92
base_desc = if @expected_error
@@ -122,6 +120,18 @@ def failure_message
122
120
end
123
121
elsif @error_subscriber . events . empty?
124
122
return 'Expected the block to report an error, but none was reported.'
123
+ elsif actual_error . nil?
124
+ # Errors were reported but none matched our expectations
125
+ reported_errors = @error_subscriber . events . map { |event | "#{ event . error . class } : '#{ event . error . message } '" } . join ( ', ' )
126
+ if @expected_error && @expected_message
127
+ return "Expected error to be an instance of #{ @expected_error } with message '#{ @expected_message } ', but got: #{ reported_errors } "
128
+ elsif @expected_error
129
+ return "Expected error to be an instance of #{ @expected_error } , but got: #{ reported_errors } "
130
+ elsif @expected_message . is_a? ( Regexp )
131
+ return "Expected error message to match #{ @expected_message } , but got: #{ reported_errors } "
132
+ elsif @expected_message . is_a? ( String )
133
+ return "Expected error message to be '#{ @expected_message } ', but got: #{ reported_errors } "
134
+ end
125
135
else
126
136
if @expected_error && !actual_error . is_a? ( @expected_error )
127
137
return "Expected error to be an instance of #{ @expected_error } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
@@ -148,28 +158,30 @@ def failure_message_when_negated
148
158
149
159
private
150
160
151
- # Check if the reported error matches our class and message expectations
161
+ # Check if any of the reported errors matches our class and message expectations
152
162
def error_matches_expectation?
153
163
return false if @error_subscriber . events . empty?
154
164
return true if @expected_error . nil? && @expected_message . nil?
155
165
156
- error_class_matches? && error_message_matches?
166
+ @error_subscriber . events . any? do |event |
167
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
168
+ end
157
169
end
158
170
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 )
171
+ # Check if the given error class matches the expected error class
172
+ def error_class_matches? ( error )
173
+ @expected_error . nil? || error . is_a? ( @expected_error )
162
174
end
163
175
164
- # Check if the actual error message matches the expected message pattern
165
- def error_message_matches?
176
+ # Check if the given error message matches the expected message pattern
177
+ def error_message_matches? ( error )
166
178
return true if @expected_message . nil?
167
179
168
180
case @expected_message
169
181
when Regexp
170
- actual_error . message &.match ( @expected_message )
182
+ error . message &.match ( @expected_message )
171
183
when String
172
- actual_error . message == @expected_message
184
+ error . message == @expected_message
173
185
else
174
186
false
175
187
end
@@ -179,13 +191,42 @@ def attributes_match_if_specified?
179
191
return true if @attributes . empty?
180
192
return false if @error_subscriber . events . empty?
181
193
182
- event_context = @error_subscriber . events . last . attributes [ :context ]
194
+ matching_event = find_matching_event
195
+ return false unless matching_event
196
+
197
+ event_context = matching_event . attributes [ :context ]
183
198
attributes_match? ( event_context )
184
199
end
185
200
186
- # Get the actual error that was reported (cached)
201
+ # Get the first matching error from all reported events (cached)
187
202
def actual_error
188
- @actual_error ||= ( @error_subscriber . events . empty? ? nil : @error_subscriber . events . last . error )
203
+ @actual_error ||= find_matching_error
204
+ end
205
+
206
+ # Find the first error that matches our expectations
207
+ def find_matching_error
208
+ return nil if @error_subscriber . events . empty?
209
+
210
+ if @expected_error . nil? && @expected_message . nil?
211
+ return @error_subscriber . events . first . error
212
+ end
213
+
214
+ @error_subscriber . events . find do |event |
215
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
216
+ end &.error
217
+ end
218
+
219
+ # Find the first event that matches our expectations
220
+ def find_matching_event
221
+ return nil if @error_subscriber . events . empty?
222
+
223
+ if @expected_error . nil? && @expected_message . nil?
224
+ return @error_subscriber . events . first
225
+ end
226
+
227
+ @error_subscriber . events . find do |event |
228
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
229
+ end
189
230
end
190
231
191
232
def attributes_match? ( actual )
0 commit comments