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?