Skip to content

Commit e1ab316

Browse files
author
Stanislav (Stas) Katkov
committed
Use ErrorCollector from Rails instead of self-written version'
1 parent c1816e2 commit e1ab316

File tree

3 files changed

+36
-49
lines changed

3 files changed

+36
-49
lines changed

lib/rspec/rails/matchers/have_reported_error.rb

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1+
require 'active_support/testing/error_reporter_assertions'
2+
13
module 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

log/development.log

Whitespace-only changes.

spec/rspec/rails/matchers/have_reported_error_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ class AnotherTestError < StandardError; end
9898
expect {
9999
Rails.error.report(StandardError.new("test"), context: { user_id: 123, context: "actual" })
100100
}.to have_reported_error.with_context(user_id: 456, context: "expected")
101-
}.to fail_with(/Expected error attributes to match {user_id: 456, context: "expected"}, but got these mismatches: {user_id: 456, context: "expected"} and actual values are {"user_id" => 123, "context" => "actual"}/)
101+
}.to fail_with(/Expected error attributes to match.*user_id.*456.*context.*expected.*got these mismatches.*user_id.*456.*context.*expected.*actual values are.*user_id.*123.*context.*actual/)
102102
end
103103

104104
it "identifies partial attribute mismatches correctly" do
105105
expect {
106106
expect {
107107
Rails.error.report(StandardError.new("test"), context: { user_id: 123, status: "active", role: "admin" })
108108
}.to have_reported_error.with_context(user_id: 456, status: "active") # user_id wrong, status correct
109-
}.to fail_with(/got these mismatches: {user_id: 456}/)
109+
}.to fail_with(/got these mismatches:.*user_id.*456/)
110110
end
111111

112112
it "handles RSpec matcher mismatches in failure messages" do
@@ -122,7 +122,7 @@ class AnotherTestError < StandardError; end
122122
expect {
123123
Rails.error.report(StandardError.new("test"), context: { user_id: 123, context: "actual" })
124124
}.to have_reported_error.with_context(user_id: 456)
125-
}.to fail_with(/actual values are {"user_id" => 123, "context" => "actual"}/)
125+
}.to fail_with(/actual values are.*user_id.*123.*context.*actual/)
126126
end
127127
end
128128

@@ -154,7 +154,7 @@ class AnotherTestError < StandardError; end
154154
expect {
155155
Rails.error.report(StandardError.new("test"), context: { user_id: 123, context: "actual" })
156156
}.to have_reported_error.with_context(user_id: 456, context: "expected")
157-
}.to fail_with(/Expected error attributes to match {user_id: 456, context: "expected"}, but got these mismatches: {user_id: 456, context: "expected"} and actual values are {"user_id" => 123, "context" => "actual"}/)
157+
}.to fail_with(/Expected error attributes to match.*user_id.*456.*context.*expected.*got these mismatches.*user_id.*456.*context.*expected.*actual values are.*user_id.*123.*context.*actual/)
158158
end
159159

160160
it "fails when no error is reported but attributes are expected" do

0 commit comments

Comments
 (0)