Skip to content

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

skatkov
Copy link

@skatkov skatkov commented Jun 4, 2025

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

@example Checking for any error
  expect { Rails.error.report(StandardError.new) }.to have_reported_error

@example Checking for specific error class
  expect { Rails.error.report(MyError.new) }.to have_reported_error(MyError)

@example Checking for specific error instance with message
  expect { Rails.error.report(MyError.new("message")) }.to have_reported_error(MyError.new("message"))

@example Checking error attributes
  expect { Rails.error.report(StandardError.new, context: "test") }.to have_reported_error.with(context: "test")

@example Checking error message patterns
  expect { Rails.error.report(StandardError.new("test message")) }.to have_reported_error(/test/)

@example Negation
  expect { "safe code" }.not_to have_reported_error

@skatkov skatkov force-pushed the have-reported-error branch from 8b837a8 to d2d0e51 Compare June 7, 2025 20:44
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

end

after do
subscribers.clear
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unlikely to be needed, as ‘let’ will initialize a bew array each time, won’t it?

let(:mock_error_reporter) { double("ErrorReporter") }
let(:subscribers) { [] }

before do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do without this? Why is this mocking needed?

Rails.error.report(StandardError.new("test error"))
end

expect(test_block).to have_reported_error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suspiciously looks like passing an argument, and like the “value expectations” syntax.

Do you think a proper “this matcher doesn’t work with value expectations “ error will be shown to users on an attempt to use it mistakenly?

test_block = proc { "no error" }
matcher = have_reported_error

expect(matcher.matches?(test_block)).to be false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason not to use the purposed matcher’s usage form like:

expect {
  expect { “no error reported” }.to have_reported_error
}.to fail_with(/failure message/)

}.to raise_error("unexpected error")
end
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be tested directly, by adding a spec with expect { } with this matcher.

Rails.error.report(TestError.new("test"), user_id: 123)
end

expect(test_block).to have_reported_error(TestError).and have_reported_error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really pass when two errors were reported and we expect to have those two reported? Does the matcher fail if either of the errors was not reported?


```ruby
# passes if Rails.error.report was called with any error
expect { Rails.error.report(StandardError.new) }.to have_reported_error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file is supposed to contain just the outline of the matchers defined here (and at a glance it’s rather incomplete). What do you think of keeping just one example of usage of this matcher? The rest will be taken into the same documentation from the feature file.


```ruby
# passes if Rails.error.report was called with any error
expect { Rails.error.report(StandardError.new) }.to have_reported_error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a realistic example that a literal ‘Rails.error.report’ will be called? Or it’s rather some application logic that will bubble up the exception to eg a controller filter that would do that?

Would it be reasonable to give such a real-world-like example here, eg (borrowed from Rails guides) have_reported_error { process_service } ?

# expect { "safe code" }.not_to have_reported_error
#
# @param expected_error [Class, Exception, Regexp, Symbol, nil] the expected error to match
def have_reported_error(expected_error = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually provide an alias to accommodate both styles, like ‘send_email’ and ‘have_sent_email’. Would you be opposed to have ‘reports_error’ as an alias?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Rails 7.1 error reporter
2 participants