Skip to content

Commit c77fca5

Browse files
committed
switch to error_report matcher type of api
1 parent 1b6deb6 commit c77fca5

File tree

4 files changed

+101
-83
lines changed

4 files changed

+101
-83
lines changed

features/matchers/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,14 @@ expect { Rails.error.report(MyError.new("message")) }.to have_reported_error(MyE
4040
# passes when specific error class with message matching pattern is reported
4141
expect { Rails.error.report(MyError.new("test message")) }.to have_reported_error(MyError, /test/)
4242

43+
# passes when any error with exact message is reported
44+
expect { Rails.error.report(StandardError.new("exact message")) }.to have_reported_error("exact message")
45+
46+
# passes when any error with message matching pattern is reported
47+
expect { Rails.error.report(StandardError.new("test message")) }.to have_reported_error(/test/)
48+
4349
# passes when error is reported with specific context attributes
4450
expect { Rails.error.report(StandardError.new, context: { user_id: 123 }) }.to have_reported_error.with_context(user_id: 123)
4551

46-
# backward compatibility - accepts error instances
47-
expect { Rails.error.report(MyError.new("message")) }.to have_reported_error(MyError.new("message"))
52+
4853
```

features/matchers/have_reported_error_matcher.feature

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ Feature: `have_reported_error` matcher
22

33
The `have_reported_error` matcher is used to check if an error was reported
44
to Rails error reporting system (`Rails.error`). It can match against error
5-
classes, instances, messages, and attributes.
5+
classes, messages, and attributes.
66

77
The matcher supports several matching strategies:
88
* Any error reported
99
* A specific error class
10-
* Specific error instance with message
10+
* A specific error class with message
1111
* Error message patterns using regular expressions
12-
* Error attributes using `.with()`
13-
* Symbol errors
12+
* Message-only matching (any class)
13+
* Error attributes using `.with_context()`
1414

1515
The matcher is available in all spec types where Rails error reporting is used.
1616

@@ -24,7 +24,7 @@ Feature: `have_reported_error` matcher
2424
end
2525
2626
def self.process_with_context
27-
Rails.error.report(ArgumentError.new("Invalid input"), context: "user_processing", severity: "high")
27+
Rails.error.report(ArgumentError.new("Invalid input"), context: { context: "user_processing", severity: :error })
2828
end
2929
3030
def self.process_custom_error
@@ -49,6 +49,28 @@ Feature: `have_reported_error` matcher
4949
When I run `rspec spec/models/user_spec.rb`
5050
Then the examples should all pass
5151

52+
Scenario: Checking for message-only matching
53+
Given a file named "spec/models/user_spec.rb" with:
54+
"""ruby
55+
require "rails_helper"
56+
57+
RSpec.describe User do
58+
it "reports error with exact message (any class)" do
59+
expect {
60+
User.process_data
61+
}.to have_reported_error("Processing failed")
62+
end
63+
64+
it "reports error with message pattern (any class)" do
65+
expect {
66+
User.process_custom_error
67+
}.to have_reported_error(/Email/)
68+
end
69+
end
70+
"""
71+
When I run `rspec spec/models/user_spec.rb`
72+
Then the examples should all pass
73+
5274
Scenario: Checking for a specific error class
5375
Given a file named "spec/models/user_spec.rb" with:
5476
"""ruby
@@ -71,7 +93,7 @@ Feature: `have_reported_error` matcher
7193
When I run `rspec spec/models/user_spec.rb`
7294
Then the examples should all pass
7395

74-
Scenario: Checking for specific error instance with message
96+
Scenario: Checking for specific error class with message
7597
Given a file named "spec/models/user_spec.rb" with:
7698
"""ruby
7799
require "rails_helper"
@@ -80,13 +102,13 @@ Feature: `have_reported_error` matcher
80102
it "reports error with specific message" do
81103
expect {
82104
User.process_data
83-
}.to have_reported_error(StandardError.new("Processing failed"))
105+
}.to have_reported_error(StandardError, "Processing failed")
84106
end
85107
86108
it "reports ArgumentError with specific message" do
87109
expect {
88110
User.process_with_context
89-
}.to have_reported_error(ArgumentError.new("Invalid input"))
111+
}.to have_reported_error(ArgumentError, "Invalid input")
90112
end
91113
end
92114
"""
@@ -99,17 +121,23 @@ Feature: `have_reported_error` matcher
99121
require "rails_helper"
100122
101123
RSpec.describe User do
102-
it "reports errors with a message matching a pattern" do
124+
it "reports errors with a message matching a pattern (any class)" do
103125
expect {
104126
User.process_data
105127
}.to have_reported_error(/Processing/)
106128
end
129+
130+
it "reports specific class with message matching a pattern" do
131+
expect {
132+
User.process_data
133+
}.to have_reported_error(StandardError, /Processing/)
134+
end
107135
end
108136
"""
109137
When I run `rspec spec/models/user_spec.rb`
110138
Then the examples should all pass
111139

112-
Scenario: Constraining error matches to their attributes using `with`
140+
Scenario: Constraining error matches to their attributes using `with_context`
113141
Given a file named "spec/models/user_spec.rb" with:
114142
"""ruby
115143
require "rails_helper"
@@ -118,13 +146,13 @@ Feature: `have_reported_error` matcher
118146
it "reports error with specific context" do
119147
expect {
120148
User.process_with_context
121-
}.to have_reported_error.with(context: "user_processing")
149+
}.to have_reported_error.with_context(context: "user_processing")
122150
end
123151
124152
it "reports error with multiple attributes" do
125153
expect {
126154
User.process_with_context
127-
}.to have_reported_error(ArgumentError).with(context: "user_processing", severity: "high")
155+
}.to have_reported_error(ArgumentError).with_context(context: "user_processing", severity: :error)
128156
end
129157
end
130158
"""
@@ -140,13 +168,13 @@ Feature: `have_reported_error` matcher
140168
it "reports a ValidationError" do
141169
expect {
142170
User.process_custom_error
143-
}.to have_reported_error(ValidationError)
171+
}.to have_reported_error(User::ValidationError)
144172
end
145173
146174
it "reports ValidationError with specific message" do
147175
expect {
148176
User.process_custom_error
149-
}.to have_reported_error(ValidationError.new("Email is invalid"))
177+
}.to have_reported_error(User::ValidationError, "Email is invalid")
150178
end
151179
end
152180
"""

lib/rspec/rails/matchers/have_reported_error.rb

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,16 @@ def report(error, **attrs)
2323
# @api private
2424
# @see RSpec::Rails::Matchers#have_reported_error
2525
class HaveReportedError < RSpec::Rails::Matchers::BaseMatcher
26-
def initialize(expected_error_class = nil, expected_message = nil)
27-
# Handle backward compatibility with old API
28-
if expected_error_class.is_a?(Exception)
29-
@expected_error_class = expected_error_class.class
30-
@expected_message = expected_error_class.message.empty? ? nil : expected_error_class.message
31-
elsif expected_error_class.is_a?(Regexp)
26+
def initialize(expected_error_or_message = nil, expected_message = nil)
27+
if expected_error_or_message.is_a?(Regexp)
3228
@expected_error_class = nil
33-
@expected_message = expected_error_class
34-
elsif expected_error_class.is_a?(Symbol)
35-
@expected_error_symbol = expected_error_class
29+
@expected_message = expected_error_or_message
30+
elsif expected_error_or_message.is_a?(String)
3631
@expected_error_class = nil
37-
@expected_message = nil
32+
@expected_message = expected_error_or_message
3833
else
39-
@expected_error_class = expected_error_class
34+
@expected_error_class = expected_error_or_message
4035
@expected_message = expected_message
41-
@expected_error_symbol = nil
4236
end
4337

4438
@attributes = {}
@@ -77,22 +71,26 @@ def supports_block_expectations?
7771
end
7872

7973
def description
80-
desc = "report an error"
81-
if @expected_error_symbol
82-
desc = "report #{@expected_error_symbol}"
83-
elsif @expected_error_class
84-
desc = "report a #{@expected_error_class} error"
85-
end
86-
if @expected_message
87-
case @expected_message
88-
when Regexp
89-
desc += " with message matching #{@expected_message}"
90-
when String
91-
desc += " with message '#{@expected_message}'"
92-
end
93-
end
94-
desc += " with #{@attributes}" unless @attributes.empty?
95-
desc
74+
base_desc = if @expected_error_class
75+
"report a #{@expected_error_class} error"
76+
else
77+
"report an error"
78+
end
79+
80+
message_desc = if @expected_message
81+
case @expected_message
82+
when Regexp
83+
" with message matching #{@expected_message}"
84+
when String
85+
" with message '#{@expected_message}'"
86+
end
87+
else
88+
""
89+
end
90+
91+
attributes_desc = @attributes.empty? ? "" : " with #{@attributes}"
92+
93+
base_desc + message_desc + attributes_desc
9694
end
9795

9896
def failure_message
@@ -105,9 +103,7 @@ def failure_message
105103
elsif @error_subscriber.events.empty?
106104
return 'Expected the block to report an error, but none was reported.'
107105
else
108-
if @expected_error_symbol
109-
return "Expected error to be #{@expected_error_symbol}, but got: #{actual_error}"
110-
elsif @expected_error_class && !actual_error.is_a?(@expected_error_class)
106+
if @expected_error_class && !actual_error.is_a?(@expected_error_class)
111107
return "Expected error to be an instance of #{@expected_error_class}, but got #{actual_error.class} with message: '#{actual_error.message}'"
112108
elsif @expected_message
113109
case @expected_message
@@ -136,11 +132,6 @@ def error_matches_expectation?
136132
# If no events were reported, we can't match anything
137133
return false if @error_subscriber.events.empty?
138134

139-
# Handle symbol matching (backward compatibility)
140-
if @expected_error_symbol
141-
return actual_error == @expected_error_symbol
142-
end
143-
144135
# If no constraints are given, any error should match
145136
return true if @expected_error_class.nil? && @expected_message.nil?
146137

@@ -206,23 +197,25 @@ def unmatched_attributes(actual)
206197
# @example Checking for specific error class with message
207198
# expect { Rails.error.report(MyError.new("message")) }.to have_reported_error(MyError, "message")
208199
#
209-
# @example Checking for specific error instance (backward compatibility)
210-
# expect { Rails.error.report(MyError.new("message")) }.to have_reported_error(MyError.new("message"))
200+
# @example Checking for error with exact message (any class)
201+
# expect { Rails.error.report(StandardError.new("exact message")) }.to have_reported_error("exact message")
211202
#
212-
# @example Checking error attributes
213-
# expect { Rails.error.report(StandardError.new, context: "test") }.to have_reported_error.with_context(context: "test")
203+
# @example Checking for error with message pattern (any class)
204+
# expect { Rails.error.report(StandardError.new("test message")) }.to have_reported_error(/test/)
214205
#
215-
# @example Checking error message patterns
206+
# @example Checking for specific error class with message pattern
216207
# expect { Rails.error.report(StandardError.new("test message")) }.to have_reported_error(StandardError, /test/)
217-
# expect { Rails.error.report(StandardError.new("test message")) }.to have_reported_error(/test/)
208+
#
209+
# @example Checking error attributes
210+
# expect { Rails.error.report(StandardError.new, context: "test") }.to have_reported_error.with_context(context: "test")
218211
#
219212
# @example Negation
220213
# expect { "safe code" }.not_to have_reported_error
221214
#
222-
# @param expected_error_class [Class, Exception, Regexp, Symbol, nil] the expected error class to match, or error instance for backward compatibility
215+
# @param expected_error_or_message [Class, String, Regexp, nil] the expected error class, message string, or message pattern
223216
# @param expected_message [String, Regexp, nil] the expected error message to match
224-
def have_reported_error(expected_error_class = nil, expected_message = nil)
225-
HaveReportedError.new(expected_error_class, expected_message)
217+
def have_reported_error(expected_error_or_message = nil, expected_message = nil)
218+
HaveReportedError.new(expected_error_or_message, expected_message)
226219
end
227220

228221
alias_method :reports_error, :have_reported_error

spec/rspec/rails/matchers/have_reported_error_spec.rb

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -152,41 +152,33 @@ class AnotherTestError < StandardError; end
152152
end
153153
end
154154

155-
context "backward compatibility with old API" do
156-
it "accepts Exception instances and matches class and message" do
155+
context "constrained by message only" do
156+
it "passes when any error with exact message is reported" do
157157
expect {
158-
Rails.error.report(TestError.new("exact message"))
159-
}.to have_reported_error(TestError.new("exact message"))
158+
Rails.error.report(StandardError.new("exact message"))
159+
}.to have_reported_error("exact message")
160160
end
161161

162-
it "accepts Exception instances with empty message and matches any message of that class" do
162+
it "passes when any error with message matching pattern is reported" do
163163
expect {
164-
Rails.error.report(TestError.new("any message"))
165-
}.to have_reported_error(TestError.new(""))
166-
end
167-
168-
it "accepts Regexp directly for message matching" do
169-
expect {
170-
Rails.error.report(StandardError.new("error with pattern"))
164+
Rails.error.report(AnotherTestError.new("error with pattern"))
171165
}.to have_reported_error(/with pattern/)
172166
end
173167

174-
175-
176-
it "fails when Exception instance class doesn't match" do
168+
it "fails when no error with exact message is reported" do
177169
expect {
178170
expect {
179-
Rails.error.report(AnotherTestError.new("message"))
180-
}.to have_reported_error(TestError.new("message"))
181-
}.to fail_with(/Expected error to be an instance of TestError, but got AnotherTestError/)
171+
Rails.error.report(StandardError.new("actual message"))
172+
}.to have_reported_error("expected message")
173+
}.to fail_with(/Expected error message to be 'expected message', but got: 'actual message'/)
182174
end
183175

184-
it "fails when Exception instance message doesn't match" do
176+
it "fails when no error with matching pattern is reported" do
185177
expect {
186178
expect {
187-
Rails.error.report(TestError.new("actual message"))
188-
}.to have_reported_error(TestError.new("expected message"))
189-
}.to fail_with(/Expected error message to be 'expected message', but got: 'actual message'/)
179+
Rails.error.report(StandardError.new("error without match"))
180+
}.to have_reported_error(/different pattern/)
181+
}.to fail_with(/Expected error message to match/)
190182
end
191183
end
192184

0 commit comments

Comments
 (0)