I like to be extremely judicious with use of routes. Fewer routes means less memory consumption and fewer confusing magical methods.
I always delete the default route map.connect ':controller/:action/:id' (you should too, otherwise all your pretty RESTful routing is easily circumvented). Since Rails now has the ability to remove unneeded RESTful routes, I’ve been removing those, too.
However, this judiciousness recently painted me into a corner. I have a controller action that I would like to test and it’s wired up like this:
map.logout '/logout', :controller => 'user_sessions', :action => 'destroy', :method => 'delete'
I don’t have this mapped any other way, because why should I?
1
2
3
4
5
6
7
8
|
def test_logout_should_redirect_to_root_path
UserSession.create(User.first)
delete :destroy
assert_match /logged out/, flash[:notice]
assert_redirected_to root_path
end
|
Unfortunately, the test fails with ActionController::RoutingError: No route matches {:action=>"destroy", :controller=>"user_sessions"}! Huh?
The problem is that the delete (and get, post, etc.) method can’t find the route that I created.
Initially, I worked around this using with_routing to define a whole new set of routes just for that test.
1
2
3
4
5
6
7
8
9
10
11
|
with_routing do |set|
set.draw do |map|
map.resource :user_sessions, :only => [:destroy]
map.root :controller => 'foobars', :action => 'index'
end
delete :destroy
assert_match /logged out/, flash[:notice]
assert_redirected_to root_path
end
|
But that was annoying. And after I had more than one route exhibiting this problem, it got really annoying.
Fortunately, I found Sam Ruby’s post Keeping Up With Rails about the challenge of Rails’ minor, quasi-documented API changes. Sam’s post has a bit about how you can add new routes without clearing the existing routes in Rails 2.3.2, which I knew was possible. Following Sam’s link to the commit (there’s no docs for this) showed how to do it.
Now, I’ve added this to test_helper.rb:
1
2
3
4
|
class ActionController::TestCase
# add a catch-all route for the tests only.
ActionController::Routing::Routes.draw { |map| map.connect ':controller/:action/:id' }
end
|
The downside to this is that real problems with broken routes may get swept under the rug. You could be more restrictive with the routes you are adding just for tests to overcome that problem.
Update: Thanks to Adam Cigánek in the comments for pointing out my error in why the route didn’t get picked up in the tests. I had the condition hash wrong!
Instead of:
map.logout '/logout', :controller => 'user_sessions', :action => 'destroy', :method => 'delete'
It should be:
map.logout '/logout', :controller => 'user_sessions', :action => 'destroy', :conditions => {:method => :delete}
The first way I had worked correctly when testing manually, but only because without :method, the route responds to all HTTP methods (still no clue why my test didn’t pick it up, though).
Interestingly enough, there’s another gotcha here. Notice that I specified :method => 'delete'. Even when put into the :conditions hash, that doesn’t work. You MUST pass a symbol (:delete) for the HTTP method.
This fixed my problem, but if I ever do need to add routes for tests, now I know how…