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,6 +114,17 @@ 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 } '"
@@ -148,44 +151,52 @@ def failure_message_when_negated
148151
149152 private
150153
151- # Check if the reported error matches our class and message expectations
152154 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?
155156
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
157160 end
158161
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 )
162164 end
163165
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 )
166168 return true if @expected_message . nil?
167-
169+
168170 case @expected_message
169171 when Regexp
170- actual_error . message &.match ( @expected_message )
172+ error . message &.match ( @expected_message )
171173 when String
172- actual_error . message == @expected_message
174+ error . message == @expected_message
173175 else
174176 false
175177 end
176178 end
177179
178180 def attributes_match_if_specified?
179181 return true if @attributes . empty?
180- return false if @error_subscriber . events . empty?
182+ return false unless matching_event
181183
182- event_context = @error_subscriber . events . last . attributes [ :context ]
184+ event_context = matching_event . attributes [ :context ]
183185 attributes_match? ( event_context )
184186 end
185187
186- # Get the actual error that was reported (cached)
187188 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
189200 end
190201
191202 def attributes_match? ( actual )
0 commit comments