Route Tests in Rails

Why test routes?

Route tests in Rails may seem like a waste of time.

I recently implemented full test coverage of our routes file for the Rails app I work on at my day job.
Our app started with Rails 2.3 and when we upgraded to Rails 3.1 we used the Rails Legacy Mapper gem to avoid completely rewriting our routes file.

I have been tasked with getting us from Rails 3.1 to Rails 4.X, which means I will now need to rewrite the routes file.

I found them a lot more useful than simply asserting the same routes still exist.

Routes to nowhere

The Rails app I work on is 5 years old and has thousands of routes. There are several reasons why unused routes may exist.

For example:

  • People not realizing the routes that may have been created for them by Rails
  • Features that have had the majority of code removed but the routes still remain.

For whatever reason, these routes probably exist in your application and they are simply cruft. Route tests can help eliminate crufy routes. For example, if a controller does not exist, the corresponding route test(s) will fail. It would be nice if a route test would fail if the supplied action does not exist, unfortunately they still pass.

Route tests will probably only help your team going forward to notice superfluous routes generated by Rails. If you require your team to write tests for any routes being created, hopefully any extraneous routes will be caught by the people creating them or in code review. We have found requiring route tests make you think a bit more about what is actually being generated by Rails when creating new routes.

Duplicate/Ambiguous routes

Unlike Rails 4.X, previous versions of Rails did not require you to specify an HTTP verb for routes. It is very easy to have routes that duplicate or overwrite each other.

These routes are more of an annoyance than a major problem. The biggest issue they may cause is to make your routes file further order dependent.

Route tests will help point out when routes are conflicting as the test will hit the first match in the routes file.

When you have conflicting routes you will only be able to use assert_generates, instead of assert_routing, and not test the route both ways.

Automagical Route Test Generation™

Looking at the output from rake routes

users GET    /users(.:format)          users#index

Then looking at the tests for those routes

assert_routing({ path: users_path, method: "GET" }, { controller: 'users', action: 'index' })
assert_routing({ path: '/users', method: "GET" }, { controller: 'users', action: 'index' })

We can see a lot of similarities here, so I borrowed a few things from the rake task built into Rails which outputs the routes. Through a few fancy regular expressions and some pretty ugly code I was able to generate most of the route tests required. I hope to have this code cleaned up and turned into a Rails plugin.