1- require "rspec/rails/matchers/base_matcher"
2-
31module RSpec
42 module Rails
53 module Matchers
@@ -26,25 +24,20 @@ def report(error, **attrs)
2624 # Matcher class for `have_reported_error`. Should not be instantiated directly.
2725 #
2826 # 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.
3027 #
3128 # @api private
3229 # @see RSpec::Rails::Matchers#have_reported_error
3330 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.
3833 #
39- # @param expected_error_or_message [Class, String, Regexp, nil]
34+ # @param expected_error_or_message [Class, String, Regexp, nil]
4035 # Error class, message string, or message pattern
41- # @param expected_message [String, Regexp, nil]
36+ # @param expected_message [String, Regexp, nil]
4237 # Expected message when first param is a class
4338 def initialize ( expected_error_or_message = UndefinedValue , expected_message = nil )
44- @actual_error = nil
4539 @attributes = { }
46- @error_subscriber = nil
47-
40+
4841 case expected_error_or_message
4942 when nil , UndefinedValue
5043 @expected_error = nil
@@ -67,7 +60,6 @@ def and(_)
6760 raise ArgumentError , "Chaining is not supported"
6861 end
6962
70- # Check if the block reports an error matching our expectations
7163 def matches? ( block )
7264 if block . nil?
7365 raise ArgumentError , "this matcher doesn't work with value expectations"
@@ -122,16 +114,22 @@ def failure_message
122114 end
123115 elsif @error_subscriber . events . empty?
124116 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
125128 else
126129 if @expected_error && !actual_error . is_a? ( @expected_error )
127130 return "Expected error to be an instance of #{ @expected_error } , but got #{ actual_error . class } with message: '#{ actual_error . message } '"
128131 elsif @expected_message
129- case @expected_message
130- when Regexp
131- return "Expected error message to match #{ @expected_message } , but got: '#{ actual_error . message } '"
132- when String
133- return "Expected error message to be '#{ @expected_message } ', but got: '#{ actual_error . message } '"
134- end
132+ return "Expected error message to #{ @expected_message . is_a? ( Regexp ) ? "match" : "be" } #{ @expected_message } , but got: '#{ actual_error . message } '"
135133 else
136134 return "Expected specific error, but got #{ actual_error . class } with message: '#{ actual_error . message } '"
137135 end
@@ -148,44 +146,52 @@ def failure_message_when_negated
148146
149147 private
150148
151- # Check if the reported error matches our class and message expectations
152149 def error_matches_expectation?
153- return false if @error_subscriber . events . empty?
154- return true if @expected_error . nil? && @expected_message . nil?
150+ return true if @expected_error . nil? && @expected_message . nil? && @error_subscriber . events . count . positive?
155151
156- error_class_matches? && error_message_matches?
152+ @error_subscriber . events . any? do |event |
153+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
154+ end
157155 end
158156
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 )
157+ def error_class_matches? ( error )
158+ @expected_error . nil? || error . is_a? ( @expected_error )
162159 end
163160
164- # Check if the actual error message matches the expected message pattern
165- def error_message_matches?
161+ # Check if the given error message matches the expected message pattern
162+ def error_message_matches? ( error )
166163 return true if @expected_message . nil?
167-
164+
168165 case @expected_message
169166 when Regexp
170- actual_error . message &.match ( @expected_message )
167+ error . message &.match ( @expected_message )
171168 when String
172- actual_error . message == @expected_message
169+ error . message == @expected_message
173170 else
174171 false
175172 end
176173 end
177174
178175 def attributes_match_if_specified?
179176 return true if @attributes . empty?
180- return false if @error_subscriber . events . empty?
177+ return false unless matching_event
181178
182- event_context = @error_subscriber . events . last . attributes [ :context ]
179+ event_context = matching_event . attributes [ :context ]
183180 attributes_match? ( event_context )
184181 end
185182
186- # Get the actual error that was reported (cached)
187183 def actual_error
188- @actual_error ||= ( @error_subscriber . events . empty? ? nil : @error_subscriber . events . last . error )
184+ @actual_error ||= matching_event &.error
185+ end
186+
187+ def matching_event
188+ @matching_event ||= find_matching_event
189+ end
190+
191+ def find_matching_event
192+ @error_subscriber . events . find do |event |
193+ error_class_matches? ( event . error ) && error_message_matches? ( event . error )
194+ end
189195 end
190196
191197 def attributes_match? ( actual )
0 commit comments