Testing rescue_action_in_public with RSpec

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?

Comments & TrackBacks ()

Paper doll icon
Eric Pugh's Gravatar

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!

Posted by: Eric Pugh on August 22, 2007 9pm

You can subscribe to the RSS feed for comments on this post.

Sorry, this entry is no longer accepting comments. If you have something you really want to say, you can write me.