Testing Routes with RSpec
Testing routes can give you more confidence and help drive application development; here is how to do it with RSpec
Routing specs are one of the less-talked-about types of spec in RSpec’s toolbox. They offer you a speedy way to verify that specific URLs go to the correct controllers.
If you are using the standard RESTful routes that Rails defaults to, then the chances are you won’t get a tonne of lasting value from these tests; however, there are two situations where you will get lots of value;
- You are new to Rails, or someone on your team is, and the default routing is not clear
- You have complicated routes
You don’t get the default routing
When I first started using Rails, I was constantly confused by routing. What seemed basic in tutorials never seemed to stick in my head or translate well to my code.
I eventually “got” it, and now it feels like second nature.
You will get value from routing specs if it helps you drive your application forward. The “driving” part is often missed in Test Driven Development because it is about moving forward with confidence.
If adding one of these tests helps you understand things better and ultimately helps you move onto the next task, then the test adds value.
You have complicated routes
You might have complicated, deeply nested routes because your application is genuinely that complicated, or you might have inherited a codebase where lots of people have made inconsistent decisions along the way. Either way, a quick test can help with understanding and also stop regression.
Wouldn’t controller tests do this?
There are two reasons why I wouldn’t recommend using a controller test when you care about the routes.
- Routing specs are fast and have barely any setup
- Controller specs can get large at the best of times; adding routing tests into them will muddy the waters
How to use routing specs
There are two ways to tell RSpec you are dealing with a routing spec.
The first is to add
type: :routing to your main describe block, like so;
RSpec.describe "routes for Articles", type: :routing do # tests go here end
The second is if you have the setting
config.infer_spec_type_from_file_location! enabled for RSpec, then any file you place inside
spec/routing will be treated as a routing spec.
I prefer to be explicit and add
type: :routing to each spec; I find it easier than double-checking which directory a file is in.
The most common thing you want to do is check that a particular path goes to a specific controller action.
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "routes /articles to the articles controller" do expect(get("/articles")).to route_to("articles#index") end end
Our test will check that when a user visits
yoursite.com/articles, Rails will hit the index action inside the ArticlesController.
If you had a namespaced route, you could test by adding the namespace to both sides of the test, here is how you could test the admin area;
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "routes admin/articles to the admin::articles controller" do expect(get("admin/articles")).to route_to("admin/articles#index") end end
You can be more verbose about this, which is what we have to do when testing we have set a specific parameter; for example, if we wanted to see that visiting
params[:id] to “my-article” we could do;
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "routes /articles/my-article to the articles controller" do expect(get("/articles/my-article")).to route_to( controller: 'articles', action: 'show', id: 'my-article' ) end end
So far, we’ve tested that specific routes can go to their correct controller actions. Occasionally we need to do things like turn off routes or ensure that a route is not accessible. Situations like this are where
be_routable comes in useful.
You generally want to use this alongside the
not_to syntax to prove the negative.
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "does not route to articles" do expect(get("/articles")).not_to be_routable end end
If Rails suddenly can route that endpoint to a controller action, the test will fail. These failures allow you to avoid accidentally turning something back on.
be_routable is excellent at testing any constraints you have in your routes.rb file. If testing a full URL and not just a path as we have been doing, you should use the URL “test.host”.
If you have a constraint that limits one path to be https only, you could write;
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "allows https route for articles" do expect(get("https://test.host/articles")).to be_routable end it "does not allow http route for articles" do expect(get("http://test.host/articles")).not_to be_routable end end
Testing named routes
Rails gives us nice methods for talking about our routes.
articles_path will generate
/articles. If you’d like, you can test these methods instead of hard-coding the paths.
require "rails_helper" RSpec.describe "routes for Articles", type: :routing do it "routes articles_path to the articles controller" do expect(get(articles_path)).to route_to("articles#index") end end
I don’t really like doing this for standard routes as it feels a bit like testing magic, but if it helps someone on your team better understand what is happening, then there is value in it for sure.
When you’re researching how to do routing tests, you will often come across code samples that use
expect(get: '/articles) instead of what I’ve used,
expect(get('/articles')). They are identical, and it is a matter of personal preference. All of the HTTP verbs can be written as a hash or as methods.
The important thing is to be consistent.
1000 words on routing specs
When I wrote this article, I honestly thought it would be a couple of paragraphs long. It turns out I had more to see about this under-used spec than I thought! Hopefully you find it useful.