1+ require 'active_support/testing/error_reporter_assertions'
2+
13module RSpec
24 module Rails
35 module Matchers
46 # @api private
57 # Sentinel value to distinguish between no argument passed vs explicitly passed nil.
68 # This follows the same pattern as RSpec's raise_error matcher.
79 UndefinedValue = Object . new . freeze
8-
9- # @api private
10- class ErrorSubscriber
11- attr_reader :events
12-
13- ErrorEvent = Struct . new ( :error , :attributes )
14-
15- def initialize
16- @events = [ ]
17- end
18-
19- def report ( error , **attrs )
20- @events << ErrorEvent . new ( error , attrs . with_indifferent_access )
21- end
22- end
10+ ErrorCollector = ActiveSupport ::Testing ::ErrorReporterAssertions ::ErrorCollector
2311
2412 # Matcher class for `have_reported_error`. Should not be instantiated directly.
2513 #
@@ -68,17 +56,14 @@ def matches?(block)
6856
6957 warn_about_nil_error! if @warn_about_nil_error
7058
71- @error_subscriber = ErrorSubscriber . new
72- ::Rails . error . subscribe ( @error_subscriber )
73-
74- block . call
59+ @reports = ErrorCollector . record do
60+ block . call
61+ end
7562
76- return false if @error_subscriber . events . empty?
63+ return false if @reports . empty?
7764 return false unless error_matches_expectation?
7865
7966 return attributes_match_if_specified?
80- ensure
81- ::Rails . error . unsubscribe ( @error_subscriber )
8267 end
8368
8469 def supports_block_expectations?
@@ -109,16 +94,16 @@ def description
10994 end
11095
11196 def failure_message
112- if !@error_subscriber . events . empty? && !@attributes . empty?
113- event_context = @error_subscriber . events . last . attributes [ : context]
114- unmatched = unmatched_attributes ( event_context )
97+ if !@reports . empty? && !@attributes . empty?
98+ report_context = @reports . last . context
99+ unmatched = unmatched_attributes ( report_context )
115100 unless unmatched . empty?
116- return "Expected error attributes to match #{ @attributes } , but got these mismatches: #{ unmatched } and actual values are #{ event_context } "
101+ return "Expected error attributes to match #{ @attributes } , but got these mismatches: #{ unmatched } and actual values are #{ report_context } "
117102 end
118- elsif @error_subscriber . events . empty?
103+ elsif @reports . empty?
119104 return 'Expected the block to report an error, but none was reported.'
120105 elsif actual_error . nil?
121- reported_errors = @error_subscriber . events . map { |event | "#{ event . error . class } : '#{ event . error . message } '" } . join ( ', ' )
106+ reported_errors = @reports . map { |report | "#{ report . error . class } : '#{ report . error . message } '" } . join ( ', ' )
122107 if @expected_error && @expected_message
123108 return "Expected error to be an instance of #{ @expected_error } with message '#{ @expected_message } ', but got: #{ reported_errors } "
124109 elsif @expected_error
@@ -140,7 +125,7 @@ def failure_message
140125 end
141126
142127 def failure_message_when_negated
143- error_count = @error_subscriber . events . count
128+ error_count = @reports . count
144129 error_word = 'error' . pluralize ( error_count )
145130 verb = error_count == 1 ? 'has' : 'have'
146131
@@ -150,10 +135,10 @@ def failure_message_when_negated
150135 private
151136
152137 def error_matches_expectation?
153- return true if @expected_error . nil? && @expected_message . nil? && @error_subscriber . events . count . positive?
138+ return true if @expected_error . nil? && @expected_message . nil? && @reports . count . positive?
154139
155- @error_subscriber . events . any? do |event |
156- error_class_matches? ( event . error ) && error_message_matches? ( event . error )
140+ @reports . any? do |report |
141+ error_class_matches? ( report . error ) && error_message_matches? ( report . error )
157142 end
158143 end
159144
@@ -177,42 +162,44 @@ def error_message_matches?(error)
177162
178163 def attributes_match_if_specified?
179164 return true if @attributes . empty?
180- return false unless matching_event
165+ return false unless matching_report
181166
182- event_context = matching_event . attributes [ : context]
183- attributes_match? ( event_context )
167+ report_context = matching_report . context
168+ attributes_match? ( report_context )
184169 end
185170
186171 def actual_error
187- @actual_error ||= matching_event &.error
172+ @actual_error ||= matching_report &.error
188173 end
189174
190- def matching_event
191- @matching_event ||= find_matching_event
175+ def matching_report
176+ @matching_report ||= find_matching_report
192177 end
193178
194- def find_matching_event
195- @error_subscriber . events . find do |event |
196- error_class_matches? ( event . error ) && error_message_matches? ( event . error )
179+ def find_matching_report
180+ @reports . find do |report |
181+ error_class_matches? ( report . error ) && error_message_matches? ( report . error )
197182 end
198183 end
199184
200185 def attributes_match? ( actual )
201186 @attributes . all? do |key , value |
187+ actual_value = actual [ key ] || actual [ key . to_s ] || actual [ key . to_sym ]
202188 if value . respond_to? ( :matches? )
203- value . matches? ( actual [ key ] )
189+ value . matches? ( actual_value )
204190 else
205- actual [ key ] == value
191+ actual_value == value
206192 end
207193 end
208194 end
209195
210196 def unmatched_attributes ( actual )
211197 @attributes . reject do |key , value |
198+ actual_value = actual [ key ] || actual [ key . to_s ] || actual [ key . to_sym ]
212199 if value . respond_to? ( :matches? )
213- value . matches? ( actual [ key ] )
200+ value . matches? ( actual_value )
214201 else
215- actual [ key ] == value
202+ actual_value == value
216203 end
217204 end
218205 end
0 commit comments