Simon
Simon

Reputation: 352

ActionDispatch::IntegrationTest suppresses exceptions

When debugging failing integration tests, I keep running into the same problem where the exceptions raised in my code are suppressed and not shown in the testing output.

For example, for the following controller and test:

class RegistrationController::ApplicationController
  def create
    # some code that raises an exception
  end
end
class RegistrationFlowTest < ActionDispatch::IntegrationTest

  test 'user registers successfully' do
    post sign_up_path, params: { username: 'arnold', password: '123' }
    assert_response :success  
  end

end

The output is something like

Minitest::Assertion: Expected response to be a <2XX: success>, but was a <500: Internal Server Error>

Is there a way to see the exact raised exception? Instead of just the difference of HTTP response code?

Thanks!

Simon

Upvotes: 2

Views: 1561

Answers (2)

BLewis
BLewis

Reputation: 150

My recommended approach to this issue would be to actually parse the response provided by Rails (at least by default in test and development environments) which includes the stacktrace for the error and handle that in the case that your test fails. This has the advantage that it won't output the stacktrace when errors are raised that don't result in failing tests (e.g. scenarios where you are intentionally testing how failures are handled).

This little module that I've crafted will allow you to call assert_response_with_errors to assert the response to a call but output the exception message and stack trace in a readable format when the response is not what you expected.

module ActionDispatch
  module Assertions
    module CustomResponseAssertions
      # Use this method when you want to assert a response body but also print the exception
      # and full stack trace to the test console.
      # Useful when you are getting errors in integration tests but don't know what they are.
      #
      # Example:
      # user_session.post create_gene_path, params: {...}
      # user_session.assert_response_with_errors :created
      def assert_response_with_errors(type, message = nil)
        assert_response(type, message)
      rescue Minitest::Assertion => e
        message = e.message
        message += "\nException message: #{@response.parsed_body[:exception]}"
        stack_trace = @response.parsed_body[:traces][:'Application Trace'].map { |line| line[:trace] }.join "\n"
        message += "\nException stack trace start"
        message += "\n#{stack_trace}"
        message += "\nException stack trace end"
        raise Minitest::Assertion, message
      end
    end
  end
end

To use this, you need to include it into ActionDispatch::Assertions before Rails has loaded its stack in your test_helper.rb. So just prepend the include into your test_helper.rb, like this:

ActionDispatch::Assertions.include ActionDispatch::Assertions::CustomResponseAssertions
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
...

Upvotes: 1

rwold
rwold

Reputation: 2476

This will happen because Rails controllers by default handle exceptions and raise the 500 status, making the exceptions invisible to the test suite (which is very helpful if errors are raised in unit tests of a model). Options for disabling this in your test suite, or alternative workarounds, are discussed here.

The key lines of code from that link, which should be added to test/integration/integration_test_helper.rb:

ActionController::Base.class_eval do
  def perform_action
    perform_action_without_rescue
  end
end

Dispatcher.class_eval do
  def self.failsafe_response(output, status, exception = nil)
    raise exception
  end
end

EDIT: I've noticed that that link is quite old now. I'm really familiar with Rack, so whilst the first block looks ok to me, I'm not sure if the second will still be current. You might need to have a look at the relevant current Rails guide if it needs bringing up to date.

Upvotes: 0

Related Questions