sakurashinken
sakurashinken

Reputation: 4080

How to test code in rescue clause in rspec

I have the following pattern in my code:

The point is that we need errors to be silently logged, but tests need fail if there is a mistake in the code.

begin
  self.a_method_call
  some_other_object.a_method_that_has_been_refactored
rescue StandardError => e
  Rails.logger.error e.backtrace
end

If an error is thrown because of an interaction between self.a_method_call and some_other_object.a_method_that_has_been_refactored then the rescue standard error will silence any error and pass any test that is testing the code block. How can I silence the rescue so that tests will fail if there is a mistake in the code in the begin clause?

Upvotes: 2

Views: 620

Answers (1)

Schwern
Schwern

Reputation: 164729

Error logging is part of its functionality. Instead of silencing the rescue, capture the error logging.

As written you can do this by mocking the logger. Use with to configure what arguments you expect Rails.logger.error to receive. Since you don't know exactly what will be received, you can use various matchers like instance_of to check you got what backtrace returns, an Array.

it 'logs the backtrace as an error' do
  # This comes before you call the method to set up the mock which expects
  # to be called.
  expect(Rails.logger).to receive(:error)
    .with(instance_of(Array))  

  thing.some_method
end

Because this replaces Rails.logger, if anything else in the process of thing.some_method calls Rails.logger the test will fail.

We can make this easier with a small refactoring. Instead of using Rails.logger directly, make it an attribute.

class SomeClass
  attr_accessor :logger

  def initialize
    @logger = Rails.logger
  end

  def some_method
    a_method_call
    some_other_object.a_method_that_has_been_refactored
  rescue StandardError => e
    logger.error e.backtrace
  end
end

Now we can specifically mock what is returned from thing.logger.

it 'logs the backtrace as an error' do
  # This comes before you call the method to set up the mock which expects
  # to be called.
  expect(thing.logger).to receive(:error)
    .with(instance_of(Array))  

  thing.some_method
end

Upvotes: 1

Related Questions