6
votes

In Rails 3, when writing functional tests in MiniTest, I got in the habit of testing routes separately from testing my controller actions. I got the idea from the Rails Guide on Testing - Section 9: Testing Routes. However, after upgrading my application to Rails 4 I noticed that the controller action tests themselves have started balking about unknown routes if I don't supply the get|patch|post|delete method with a proper set of params.

For example, given routes:

# config/routes.rb
namespace "api" do
  namespace "v2", defaults: { format: :json } do
    resources :users do
      resources :posts do
        resources :comments
      end
    end
  end
end

And functional test:

# test/controllers/api/v2/comments_controller_test.rb
describe Api::V2::CommentsController
  it "does something" do
    get :index
  end
end

In Rails 3, the above would work. But in Rails 4 I get a URL generation error:

ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"api/v2/comments"}

From this I can infer that the get helper simply failed to match a route from the routes file when trying to locate the controller and action. Fair enough. I can fix this by changing the get call to include the parameters needed to satisfy the nested route, like this:

# test/controllers/api/v2/comments_controller_test.rb
describe Api::V2::CommentsController
  it "does something" do
    get :index, { user_id: "1", post_id: "1" }
  end
end

... then all is well again.

So my question is, since it wasn't the case in in Rails 3, is it now OK to trust the controller action tests to fully test/validate my routes in Rails 4+? Or is there additional advantage in testing the routes as well still? Is there, perhaps, some other angle that the route tests cover that the controller action tests don't cover? (Note: I'm not asking for an opinion on what's good to test; I'm asking for functional differences between route integration tests and controller action tests in regard to route requirements.)

Also, I couldn't find a specific reference to this change in behavior in the Rails 4 release notes (or in Minitest), so I'm wondering why this behavior change was made in the first place. I don't think it's a bad thing -- I think it's good -- but I feel weird not seeing it mentioned in a changelog somewhere. And I thought that half the point of the get|patch|post|delete methods were to free you from having to think about what params are needed for routing in the first place.

For completeness, here is the route test I would use for this:

describe "CommentsController Route Integration Test" do
  let(:default_options) {
    { controller: "api/v2/comments",
      user_id: "1",
      posts_id: "1",
      format: :json }
  }

  it "#index" do
    assert_routing "/api/v2/users/1/posts/1/comments",
                   default_options.merge(action: "index")
  end
end

UPDATE

I've been looking through ActionDispatch code for an answer... the only thing I can see so far is that the url_for stuff has changed a lot since Rails 3 and that the ActionController::UrlGenerationError class itself has been added in Rails 4. So it could be that these new, more strict routing requirements are incidental change to the decoupling of ActionView and ActionController.

2

2 Answers

2
votes

I'm of the belief that testing routes and controllers is totally unnecessary given you have feature tests. This does not directly answer your question, but it should solve your dilemma. I recommend doing some reading on various testing philosophies and (further) formulating an idealogical opinion on how and what to test.

2
votes

After writing functional tests for a few new controllers recently, without writing any Route Integration Tests, I'm fairly confident that the additional work of testing routes is overkill. Given, that is, if there are functional tests covering all of the routes.

My reasoning is basically that... in any case where I've supplied improper parameters to a get|patch|post|delete call into a controller action, it's always failed. And whenever I write a test for a controller action that exists but that is lacking a corresponding route it always fails. So it appears that the higher-level functional tests also exercise the lower level route integration tests just fine -- so why bother duplicating effort!

I guess I'll still be looking for an updated "official" word on this in the future, but for now I've decided to stop worrying about directly testing my routes whenever overlapping functional tests exist.

UPDATE

I've further decided that testing API routes may still be useful. This helps to ensure that the actual URL string that is published with the API is actually correct and working. Note that it's important to test the string and not the named route as the named route can change with changes to the resources in the routes file.