Testing rescue_action_in_public with RSpec

In: Ruby|Ruby on Rails

31 Mar 2007

After overriding rescue_action_in_public in the ApplicationController to deal with ActiveRecord::RecordNotFound exceptions (a very common exception to rescue in the canonical ‘show’ actions of your controllers), I decided to test it. I’ve been getting used to BDD with RSpec (and the Spec::Rails plugin), so I stumbled a bit when writing the spec.

I finally settled on this:


class DummyController < ApplicationController
  def index
  end
end

context 'A child class of ApplicationController' do
  controller_name :dummy

  specify 'should render a 404 error for ActiveRecord::RecordNotFound,
    ActionController::UnknownController,
    ActionController::UnknownAction,
    ActionController::RoutingError exceptions (in public)' do

    exceptions_404 = [
      ActionController::RoutingError.new('test'),
      ActiveRecord::RecordNotFound.new,
      ActionController::UnknownController.new,
      ActionController::UnknownAction.new]

    exceptions_404.each do |exception|
      controller.eigenclass.send(:define_method, :index) do
        raise exception.class, 'some message'
      end

      lambda {
        get 'index'
      }.should_raise(exception.class)
      controller.send :rescue_action_in_public, exception

      response.should be_missing
    end
  end
end

Notice the use of a dummy controller so that we can actually make a request to it (and get all the Rails magic and environment set up ready for testing). Also, I had to use instances of the exceptions rather than their classes because I'm sending a rescue_action_in_public message to the controller without knowing how to instantiate the exceptions (for example, ActionController::RoutingError actually has a constructor which requires at least 1 argument). So I create the exceptions first.

The eigenclass method simply returns Ruby's canonical singleton class or metaclass, depending on who you talk to (i.e. class << self; self; end;) and I modify the dummy 'index' action to raise the exception. And here's the stinky part:


lambda {
  get 'index'
}.should_raise(exception.class)
controller.send :rescue_action_in_public, exception

Make a GET to the 'index' action, make sure it raises the exception and catch it (with should_raise - the assertion is unnecessary since I did override 'index' to raise the exception), and then force rescue_action_in_public to be called. Something's fishy here - why isn't the exception caught by default by rescue_action_in_public? I've set these to make sure that rescue_action_in_public is called but it seems like it never is called:


ActionController::Base.consider_all_requests_local = false
controller.eigenclass.send(:define_method, :local_request?) do
  false
end

I traced the code into ActionController::Rescue and everything seems to be in order. I'm stumped and weary, I think I'll look at this again tomorrow. Anyone see any obvious mistakes?

1 Response to Testing rescue_action_in_public with RSpec

Avatar

Eric Pugh

August 22nd, 2007 at 9pm

Thanks for this post! I was able to use it to give me some confidence that the error handling page works! Especially since I don’t see it in the dev environment. You did a good job of explaining a relatively tricky spec simply!