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.

route_to

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 articles/my-article sets 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

be_routable

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.

Different syntax

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.


Recent posts View all

Web Dev

Creating draft posts in Jekyll

How to create and develop with draft posts in Jekyll

Ruby

Forcing a Rails database column to be not null

How you can force a table column to always have something in it with Rails