3

We have a custom exception app that has been raising (fail safe) exceptions (the application equivalent of having an exception in a rescue block).

I think I've fixed it, but am finding it hard to test. It's an unrouted controller, so I can't use controller tests (require routing).

i.e. I have Rails.configuration.exceptions_app = ExceptionController.action(:show), not Rails.configuration.exceptions_app = self.routes.

Basically what I think I need to do is

  1. Generate a test request request = ActionDispatch::TestRequest.new
  2. include Rack::Test or maybe mimic behavior in ActiveSupport::IntegrationTest
  3. Set @app = ExceptionsController.action(:show)
  4. Fake an exception request.env.merge! 'action_dispatch.exception' => ActionController::RoutingError.new(:foo)
  5. Test response = @app.call(request.env)
  6. Assert no exception is raised and correct response body and status

Problems:

The env needs

  • a warden / devise session with current_user request.env['warden'] = spy(Warden) and request.session = ActionDispatch::Integration::Session.new(@app)
  • to manipulate request formats so that I can check that a request without an accept defaults to json request.any?(:json)? constraints: { default: :json } ? `request.accept = "application/javascript"
  • work work with the respond_with responder
  • set action_dispatch.show_exceptions, consider all requests local, etc request.env["action_dispatch.show_detailed_exceptions"] = true

Also, I considered building a ActionDispatch::ShowException.new(app, ExceptionController.new) or a small rack app

But our gem has no tests and I haven't been able to apply anything that I've read in exception handling gems (most work at the rescue_action_in_public level or mix in to ShowException) or in the Rails source code

This is a Rails 4.2 app tested via Rspec and Capybara.

Thoughts, links, halp?

Example code and tests

RSpec.describe 'ExceptionController' do
  class ExceptionController < ActionController::Base
    use ActionDispatch::ShowExceptions, Rails.configuration.exceptions_app
    use ActionDispatch::DebugExceptions

    #Response
    respond_to :html, :json

    #Layout
    layout :layout_status

    #Dependencies
    before_action :status, :app_name, :log_exception

    def show
      respond_with details, status: @status, location: nil
    end

    def show_detailed_exceptions?
      request.local?
    end

    protected

    ####################
    #   Dependencies   #
    ####################

    #Info
    def status
      @exception  = env['action_dispatch.exception']
      @status     = ActionDispatch::ExceptionWrapper.new(env, @exception).status_code
      @response   = ActionDispatch::ExceptionWrapper.rescue_responses[@exception.class.name]
    end

    #Format
    def details
      @details ||= {}.tap do |h|
        I18n.with_options scope: [:exception, :show, @response], exception_name: @exception.class.name, exception_message: @exception.message do |i18n|
          h[:name]    = i18n.t "#{@exception.class.name.underscore}.title", default: i18n.t(:title, default: @exception.class.name)
          h[:message] = i18n.t "#{@exception.class.name.underscore}.description", default: i18n.t(:description, default: @exception.message)
        end
      end
    end
    helper_method :details

    ####################
    #      Layout      #
    ####################

    private

    def log_exception
      if @status.to_s == '500'
        request.env[:exception_details] = details
        request.env[:exception_details][:location] = ActionDispatch::ExceptionWrapper.new(env, @exception).application_trace[0]
      end
    end

    #Layout
    def layout_status
      @status.to_s != '404' ? 'error' : 'application'
    end

    #App
    def app_name
      @app_name = Rails.application.class.parent_name
    end

  end

  include Rack::Test::Methods
  include ActionDispatch::Integration::Runner
  include ActionController::TemplateAssertions
  include ActionDispatch::Routing::UrlFor

  let(:exception) { ActionController::RoutingError.new(:foo) }
  let(:request) { ActionDispatch::TestRequest.new }

  def app
    # Rails.application.config.exceptions_app
    @app ||= ExceptionController.action(:show)
  end

  it 'logs unknown format errors' do
    request.env['action_dispatch.show_exceptions'] = true
    request.env['consider_all_requests_local'] = true
    request.env['warden'] = spy(Warden)
    request.session =  ActionDispatch::Integration::Session.new(app)
    exception = ActionController::RoutingError.new(:foo)
    request.env.merge! 'action_dispatch.exception' => exception 

    post '/whatever'
    expect(response.body).to eq("dunno?")
  end
end

refs:

Update 2015-08-27

It has been suggested that this question may be a duplicate of Testing error pages in Rails with Rspec + Capybara, however, that question addresses testing exception responses when the exceptions_app is set to routes.

As I wrote above, I'm using a Controller as the exceptions_app, so though I could use capybara to visit non-existing pages, I'd like to test Controller's action directly, rather than include the rest of the show exceptions stack. This is important because my problem is when the exceptions app is called with an unhandled content type, which I cannot easily test via capybara.

More generally, what I need to test is when the Exceptions app raises and exception, that I have fixed it.

I'm open to seeing some example code, though.

BF4
  • 1,076
  • 7
  • 23
  • possible duplicate of [Testing error pages in Rails with Rspec + Capybara](http://stackoverflow.com/questions/13996259/testing-error-pages-in-rails-with-rspec-capybara) – eirikir Aug 25 '15 at 23:26
  • I think you may be overthinking this. You should be able to create a simple Capybara acceptance test like `visit "/some/unrouted/path"` and `expect(page).to have_content "404 error"`, you just need to the correct settings as in the answer I linked to. – eirikir Aug 25 '15 at 23:28
  • I updated the question explaining why I don't think I can test a fail safe exception with capybara – BF4 Aug 27 '15 at 18:41
  • to clarify, the exception is happening when the ExceptionController is trying to render a non-existent template, which results in a Fail-Safe response. To test this via capybara I'd need a route that raises an exception that causes the exceptions_app to render a format it doesn't know how to. I don't think that's possible. If I just wanted to verify my 404 page, I agree, that would be trivial. – BF4 Sep 02 '15 at 08:30

0 Answers0