@@ -20,23 +20,34 @@ def report(error, **attrs)
2020
2121 # Matcher class for `have_reported_error`. Should not be instantiated directly.
2222 #
23+ # Provides a way to test that an error was reported to Rails.error.
24+ # This matcher follows the same patterns as RSpec's built-in `raise_error` matcher.
25+ #
2326 # @api private
2427 # @see RSpec::Rails::Matchers#have_reported_error
2528 class HaveReportedError < RSpec ::Rails ::Matchers ::BaseMatcher
29+ # Initialize the matcher following raise_error patterns
30+ #
31+ # @param expected_error_or_message [Class, String, Regexp, nil]
32+ # Error class, message string, or message pattern
33+ # @param expected_message [String, Regexp, nil]
34+ # Expected message when first param is a class
2635 def initialize ( expected_error_or_message = nil , expected_message = nil )
27- if expected_error_or_message . is_a? ( Regexp )
28- @expected_error_class = nil
29- @expected_message = expected_error_or_message
30- elsif expected_error_or_message . is_a? ( String )
31- @expected_error_class = nil
36+ @actual_error = nil
37+ @attributes = { }
38+ @error_subscriber = nil
39+
40+ case expected_error_or_message
41+ when nil
42+ @expected_error = nil
43+ @expected_message = expected_message
44+ when String , Regexp
45+ @expected_error = nil
3246 @expected_message = expected_error_or_message
3347 else
34- @expected_error_class = expected_error_or_message
48+ @expected_error = expected_error_or_message
3549 @expected_message = expected_message
3650 end
37-
38- @attributes = { }
39- @error_subscriber = nil
4051 end
4152
4253 def with_context ( expected_attributes )
@@ -48,9 +59,10 @@ def and(_)
4859 raise ArgumentError , "Chaining is not supported"
4960 end
5061
62+ # Check if the block reports an error matching our expectations
5163 def matches? ( block )
5264 if block . nil?
53- raise ArgumentError , "this matcher doesn’ t work with value expectations"
65+ raise ArgumentError , "this matcher doesn' t work with value expectations"
5466 end
5567
5668 @error_subscriber = ErrorSubscriber . new
@@ -71,8 +83,8 @@ def supports_block_expectations?
7183 end
7284
7385 def description
74- base_desc = if @expected_error_class
75- "report a #{ @expected_error_class } error"
86+ base_desc = if @expected_error
87+ "report a #{ @expected_error } error"
7688 else
7789 "report an error"
7890 end
@@ -103,8 +115,8 @@ def failure_message
103115 elsif @error_subscriber . events . empty?
104116 return 'Expected the block to report an error, but none was reported.'
105117 else
106- if @expected_error_class && !actual_error . is_a? ( @expected_error_class )
107- return "Expected error to be an instance of #{ @expected_error_class } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
118+ if @expected_error && !actual_error . is_a? ( @expected_error )
119+ return "Expected error to be an instance of #{ @expected_error } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
108120 elsif @expected_message
109121 case @expected_message
110122 when Regexp
@@ -128,26 +140,31 @@ def failure_message_when_negated
128140
129141 private
130142
143+ # Check if the reported error matches our class and message expectations
131144 def error_matches_expectation?
132- # If no events were reported, we can't match anything
133145 return false if @error_subscriber . events . empty?
134-
135- # If no constraints are given, any error should match
136- return true if @expected_error_class . nil? && @expected_message . nil?
146+ return true if @expected_error . nil? && @expected_message . nil?
147+
148+ error_class_matches? && error_message_matches?
149+ end
137150
138- class_matches = @expected_error_class . nil? || actual_error . is_a? ( @expected_error_class )
151+ # Check if the actual error class matches the expected error class
152+ def error_class_matches?
153+ @expected_error . nil? || actual_error . is_a? ( @expected_error )
154+ end
155+
156+ # Check if the actual error message matches the expected message pattern
157+ def error_message_matches?
158+ return true if @expected_message . nil?
139159
140- message_matches = if @expected_message . nil?
141- true
142- elsif @expected_message . is_a? ( Regexp )
160+ case @expected_message
161+ when Regexp
143162 actual_error . message &.match ( @expected_message )
144- elsif @expected_message . is_a? ( String )
163+ when String
145164 actual_error . message == @expected_message
146165 else
147166 false
148167 end
149-
150- class_matches && message_matches
151168 end
152169
153170 def attributes_match_if_specified?
@@ -158,8 +175,9 @@ def attributes_match_if_specified?
158175 attributes_match? ( event_context )
159176 end
160177
178+ # Get the actual error that was reported (cached)
161179 def actual_error
162- @error_subscriber . events . empty? ? nil : @error_subscriber . events . last . error
180+ @actual_error ||= ( @ error_subscriber. events . empty? ? nil : @error_subscriber . events . last . error )
163181 end
164182
165183 def attributes_match? ( actual )
0 commit comments