Testing your custom error pages with Webrat and Cucumber

When your application raises an error during development, Rails and Webrat will provide you with useful information like the error's type and stacktrace.

But now you created that shiny, custom error page by overriding rescue_action_in_public. You would like to write a Cucumber feature that tests whether your new error page localizes into 10 languages and displays all the information it should.

Unfortunately it requires some serious Judo to make your error page visible to selected scenarios, while keeping Rails' helpful default behavior for your other tests. Here is how you do it.

First, we will tame Webrat. Webrat raises an exception when you visit a page that shows an error. So we're going to write a step that visits a page, but ignores errors:

When /^I ignore errors while visiting (.+)$/ do |page_name|
  begin
    visit path_to(page_name)
  rescue
  end
end

Now we need to get Rails to render the error page for selected scenarios. Unfortunately we will have to flip a lot of switches to accomplish this.

Put the following into your support/env.rb. With this code we can make Rails treat selected requests as remote ones, (local requests never see error pages), regardless of what consider_all_requests_local is set to:

ActionController::Base.class_eval do
  protected

  cattr_accessor :local_request_override

  def local_request_with_override?
    local_request_override.nil? ? local_request_without_override? : local_request_override
  end

  alias_method_chain :local_request?, :override
end

Finally we need a step that flips all the magic switches for the current scenario and reverts to the previous error handling afterwards:

Given /^error pages will be rendered$/ do
  @old_allow_rescue = ActionController::Base.allow_rescue
  @old_consider_all_requests_local = ActionController::Base.consider_all_requests_local
  @old_local_request_override = ActionController::Base.local_request_override
  ActionController::Base.local_request_override = false
  ActionController::Base.allow_rescue = true
  ActionController::Base.consider_all_requests_local = false
end

After do
  @old_allow_rescue.nil? or ActionController::Base.allow_rescue = @old_allow_rescue
  @old_consider_all_requests_local.nil? or ActionController::Base.consider_all_requests_local = @old_consider_all_requests_local
  @old_local_request_override.nil? or ActionController::Base.local_request_override = @old_local_request_override
end

Now you can write your error_pages.feature:

Feature: custom error pages
  In order to not scare my users when things go wrong
  As the site owner
  I want to display friendly error pages

  Scenario: A country cannot be found
    Given error pages will be rendered
    And I ignore errors while visiting the country "Narnia"
    Then I should see "Oops, we couldn't find Narnia!"

I recommend you extend your feature to cover some more cases and also make it check if proper HTTP status codes are returned.

Was this post helpful to you? Then let us know!

You can follow any response to this post through the Atom feed.

Avatar

Wed, 24 Mar 2010 21:45:00 GMT

by henning

Tags:

  • Alex Tomlins said about 1 month later:

    Alternatively, with recent versions of cucumber, you can simply tag your scenario with @allow-rescue. With this in place, you can write your scenario just like any other scenario, and it’ll work as you’d expect.

    Alex

  • Henning said about 1 month later:

    @Alex: Thank you for the pointer to the @allow-rescue tag. You still need to make those requests not be considered local, or you will see a stack trace instead of your custom error page.

  • mike said 7 months later:

    @Henning: thanks a lot! This saved quite some time. I still have some questions though.

    1) Why do I have to make the requests not to be considered local? Indeed I see a stack trace, but why?

    2) On another site (see ref below) I found the following that claims to achieve the same non-local effect (though I did not get it working):

    header “REMOTE-ADDR”, “10.0.1.1”

    Is that a sufficient replacement for your ActionControler change? Ref to the site: http://jjinux.blogspot.com/2009/08/rails-dynamic-404s-authlogic-cucumber.html

    –Mike

  • Henning said 7 months later:

    @Mike: It’s a built-in behavior of Rails that error pages are only rendered for remote requests. Requests that are considered local always get the stacktrace.

    The method on the site you mentioned requires less code than our solution, which would be preferrable. Should you find a way to get it to work, let me know.

  • krukid said 12 months later:

    @mike header “REMOTE-ADDR”, “10.0.1.1” in a “Given” clause is sufficient to spoof local_request? => false

    I’ve had a similar problem - not being able to trap my custom rescue_from ... behavior because it was wrapped in a unless consider_all_requests_local block. When my ActionController is interpreted consider_all_requests_local is true, before I can spoof it in cucumber’s support/env or a Given - so the rescue_from clauses don’t apply. To fix that behavior you can either unwrap these clauses so that they work in your test environment or duplicate them like I did in support/overrides. Not pretty, but there seems to be no other way.

Leave a comment