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,18 @@ 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
+ # Errors were reported but none matched our expectations
119
+ reported_errors = @error_subscriber . events . map { |event | "#{ event . error . class } : '#{ event . error . message } '" } . join ( ', ' )
120
+ if @expected_error && @expected_message
121
+ return "Expected error to be an instance of #{ @expected_error } with message '#{ @expected_message } ', but got: #{ reported_errors } "
122
+ elsif @expected_error
123
+ return "Expected error to be an instance of #{ @expected_error } , but got: #{ reported_errors } "
124
+ elsif @expected_message . is_a? ( Regexp )
125
+ return "Expected error message to match #{ @expected_message } , but got: #{ reported_errors } "
126
+ elsif @expected_message . is_a? ( String )
127
+ return "Expected error message to be '#{ @expected_message } ', but got: #{ reported_errors } "
128
+ end
125
129
else
126
130
if @expected_error && !actual_error . is_a? ( @expected_error )
127
131
return "Expected error to be an instance of #{ @expected_error } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
@@ -148,44 +152,61 @@ def failure_message_when_negated
148
152
149
153
private
150
154
151
- # Check if the reported error matches our class and message expectations
155
+ # Check if any of the reported errors matches our class and message expectations
152
156
def error_matches_expectation?
153
- return false if @error_subscriber . events . empty?
154
- return true if @expected_error . nil? && @expected_message . nil?
157
+ return true if @expected_error . nil? && @expected_message . nil? && @error_subscriber . events . count . positive?
155
158
156
- error_class_matches? && error_message_matches?
159
+ @error_subscriber . events . any? do |event |
160
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
161
+ end
157
162
end
158
163
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 )
164
+ # Check if the given error class matches the expected error class
165
+ def error_class_matches? ( error )
166
+ @expected_error . nil? || error . is_a? ( @expected_error )
162
167
end
163
168
164
- # Check if the actual error message matches the expected message pattern
165
- def error_message_matches?
169
+ # Check if the given error message matches the expected message pattern
170
+ def error_message_matches? ( error )
166
171
return true if @expected_message . nil?
167
-
172
+
168
173
case @expected_message
169
174
when Regexp
170
- actual_error . message &.match ( @expected_message )
175
+ error . message &.match ( @expected_message )
171
176
when String
172
- actual_error . message == @expected_message
177
+ error . message == @expected_message
173
178
else
174
179
false
175
180
end
176
181
end
177
182
178
183
def attributes_match_if_specified?
179
184
return true if @attributes . empty?
180
- return false if @error_subscriber . events . empty?
181
185
182
- event_context = @error_subscriber . events . last . attributes [ :context ]
186
+ matching_event = find_matching_event
187
+ return false unless matching_event
188
+
189
+ event_context = matching_event . attributes [ :context ]
183
190
attributes_match? ( event_context )
184
191
end
185
192
186
- # Get the actual error that was reported (cached)
193
+ # Get the first matching error from all reported events (cached)
187
194
def actual_error
188
- @actual_error ||= ( @error_subscriber . events . empty? ? nil : @error_subscriber . events . last . error )
195
+ @actual_error ||= find_matching_error
196
+ end
197
+
198
+ # Find the first error that matches our expectations
199
+ def find_matching_error
200
+ @error_subscriber . events . find do |event |
201
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
202
+ end &.error
203
+ end
204
+
205
+ # Find the first event that matches our expectations
206
+ def find_matching_event
207
+ @error_subscriber . events . find do |event |
208
+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
209
+ end
189
210
end
190
211
191
212
def attributes_match? ( actual )
0 commit comments