-
-
Notifications
You must be signed in to change notification settings - Fork 1k
WIP: Introduce have_reported_error matcher #2849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
8b837a8
to
d2d0e51
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
71685ad
to
6135d5d
Compare
Scenario: Using in controller specs | ||
Given a file named "spec/controllers/users_controller_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
RSpec.describe UsersController, type: :controller do | ||
describe "POST #create" do | ||
it "reports validation errors" do | ||
expect { | ||
post :create, params: { user: { email: "invalid" } } | ||
}.to have_reported_error(ValidationError) | ||
end | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/controllers/users_controller_spec.rb` | ||
Then the examples should all pass | ||
|
||
Scenario: Using in request specs | ||
Given a file named "spec/requests/users_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
RSpec.describe "Users", type: :request do | ||
describe "POST /users" do | ||
it "reports processing errors" do | ||
expect { | ||
post "/users", params: { user: { name: "Test" } } | ||
}.to have_reported_error.with(context: "user_creation") | ||
end | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/requests/users_spec.rb` | ||
Then the examples should all pass | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a controller / request specific matcher so these feel out of place, why these, why not every aspect of Rails... I'd just cut them.
👋 I think this would be a great addition, sorry for the size of the review its been on my todo list for a while, its mostly just grammar / wording tweaks plus a few "fit" things. The one change I do want to see though is dropping instance matching, I don't think it makes sense over just providing class / message / using with. |
@JonRowe thanks for review.
Which of these you would rather go with? or
?? I assume, that first option is prefered to keep similarity with |
Given that you already have
But if you insisted on |
It makes sense to separate matching of args passed to |
The only argument that the exception class accepts is "message". We can of course subclass it and support way more than that, but I'm not sure if we should support this in a matcher? I have a feeling, that having a matcher interface similar to |
I hadn't clocked that the extra attributes were in addition to the error, I think matching the |
Here is a source code for Rails.error.report. These attributes in rails case have a name "context". so |
f90fc3b
to
2488a71
Compare
end | ||
|
||
def failure_message | ||
if !@error_subscriber.events.empty? && !@attributes.empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cosmetic: any?
or present?
?
|
||
def failure_message | ||
if !@error_subscriber.events.empty? && !@attributes.empty? | ||
event_context = @error_subscriber.events.last.attributes[:context] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won’t it be confusing to use last
when several errors were reported?
return "Expected error message to be '#{@expected_message}', but got: #{reported_errors}" | ||
end | ||
else | ||
if @expected_error && !actual_error.is_a?(@expected_error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actual_error should be is_a? expected_class, no? Just by looking at the implementation. Or what is the case when they won’t match?
|
||
case @expected_message | ||
when Regexp | ||
error.message&.match(@expected_message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cosmetic: is safe nav necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, sometimes error
object could be nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How? Please add a spec for this case.
|
||
private | ||
|
||
def error_matches_expectation? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the last condition necessary? We already check if it’s empty?
before calling this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This particular method checks that the error matches one that we expect, not just that the error occurred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A fee minor things and simplifications and it looks good to go.
Let’s leave out matching multiple with chains, severity qualifiers, and all extras for later.
|
||
def matches?(block) | ||
if block.nil? | ||
raise ArgumentError, "this matcher doesn't work with value expectations" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this check have the same effect as defining a method supports_value_expectations?
that returns false? It should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RSpec is actually allowing the value expectation to reach the matches?
method, even with supports_value_expectations?
defined as false. This mechanism is NOT preventing the value expectation from reaching matches?
. The explicit check was necessary in my testing! But maybe I missed something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha! Nice catch. The RSpec.deprecate
call somehow didn't fail the build. I'll have a closer look.
The block
is not nil
though for expect(:foo).to reports_error
, so this check is useless in its current implementation.
I suggest removing it, and relying on the supports_value_expectations?
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recall now that we kept in 3.x the possibility to pass a lambda to expect
, or define subject
as a lambda and use is_expected.to
with block matchers. We just print a deprecation message now.
Swallowed deprecation messages are due to #2857
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the aftermentioned fix, with
def supports_value_expectations?
false
end
The following passes:
it "warns when used as a value expectation" do
expect {
expect(Rails.error.report(StandardError.new("test error"))).to have_reported_error
}.to raise_error(/implicit block expectation.+deprecated/)
end
We can change the code when that swallowed deprecation fix is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed now and any test marked as pending with the reason instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
# | ||
# @param expected_error_or_message [Class, String, Regexp, nil] the expected error class, message string, or message pattern | ||
# @param expected_message [String, Regexp, nil] the expected error message to match | ||
def have_reported_error(expected_error_or_message = UndefinedValue, expected_message = nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have default attributes both here and in the initializer. Worth leaving just here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still defaults both here and there:
def initialize(expected_error_or_message = UndefinedValue, expected_message = nil)
Let's remove there.
end | ||
def self.process_with_context | ||
Rails.error.report(ArgumentError.new("Invalid input"), context: { context: "user_processing", severity: :error }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nested context here, too. Rename inner to topic:
or :section
?
Commit grammar improvements Co-authored-by: Jon Rowe <[email protected]>
Co-authored-by: Phil Pirozhkov <[email protected]>
b5568a0
to
aa4c84d
Compare
fixes #2827
rspec-rails is missing support for Rails ErrorReporter, this was introduced to rails in v7 and has been evolving ever since. With my client, we have moved to using ErrorReporter as a unified error reporting interface, so we can easily move from one error tracking software to another with minimal code changes. And we had a need to test this interface with rspec, so we implemented our own matcher to handle this.
I'm suggesting our internal implementation as is. This is probably not suitable as is for this gem, but I'd like to open discussion with this starting point.
Example usage
TODO
Outline of things that we want to do before marking this as completed.
values_match?(@attributes, actual)
for matchermatching_reports
method.