Skip to content

Commit 2488a71

Browse files
committed
Look through all errors, don't just go with last error
1 parent 7b3abc5 commit 2488a71

File tree

2 files changed

+46
-40
lines changed

2 files changed

+46
-40
lines changed

lib/rspec/rails/matchers/have_reported_error.rb

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require "rspec/rails/matchers/base_matcher"
2-
31
module 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)

spec/rspec/rails/matchers/have_reported_error_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class AnotherTestError < StandardError; end
4848
expect {
4949
Rails.error.report(AnotherTestError.new("wrong error"))
5050
}.to have_reported_error(TestError)
51-
}.to fail_with(/Expected error to be an instance of TestError, but got AnotherTestError/)
51+
}.to fail_with(/Expected error to be an instance of TestError, but got: AnotherTestError/)
5252
end
5353
end
5454

@@ -70,7 +70,7 @@ class AnotherTestError < StandardError; end
7070
expect {
7171
Rails.error.report(TestError.new("actual message"))
7272
}.to have_reported_error(TestError, "expected message")
73-
}.to fail_with(/Expected error message to be 'expected message', but got: 'actual message'/)
73+
}.to fail_with(/Expected error to be an instance of TestError with message 'expected message', but got: TestError/)
7474
end
7575
end
7676

@@ -86,7 +86,7 @@ class AnotherTestError < StandardError; end
8686
expect {
8787
Rails.error.report(StandardError.new("error without match"))
8888
}.to have_reported_error(StandardError, /different pattern/)
89-
}.to fail_with(/Expected error message to match/)
89+
}.to fail_with(/Expected error to be an instance of StandardError with message/)
9090
end
9191
end
9292

@@ -180,15 +180,15 @@ class AnotherTestError < StandardError; end
180180
expect {
181181
Rails.error.report(StandardError.new("actual message"))
182182
}.to have_reported_error("expected message")
183-
}.to fail_with(/Expected error message to be 'expected message', but got: 'actual message'/)
183+
}.to fail_with(/Expected error message to be 'expected message', but got: StandardError/)
184184
end
185185

186186
it "fails when no error with matching pattern is reported" do
187187
expect {
188188
expect {
189189
Rails.error.report(StandardError.new("error without match"))
190190
}.to have_reported_error(/different pattern/)
191-
}.to fail_with(/Expected error message to match/)
191+
}.to fail_with(/Expected error message to match .+different pattern.+, but got: StandardError/)
192192
end
193193
end
194194

0 commit comments

Comments
 (0)