Search code examples
ruby-on-railsrspecrspec-rails

Why am I getting ActionController::UrlGenerationError: "No route matches" in my controller spec?


I have a controller spec looking like this:

# config_controller_spec.rb
require "spec_helper"

describe Api::V4::ConfigController, type: :controller do

  let(:parsed_response) { response.body.to_json }

  describe 'GET app_config' do
    it "renders successfully" do
      get :app_config
      expect(response).to be_success
      expect(parsed_response).to eq("{key: val}")
    end
  end
end

When I run it however, I get:

ActionController::UrlGenerationError:
       No route matches {:action=>"app_config", :controller=>"api/v4/config"}

I don't under stand why. I googled around and figured that if I add: use_route: :config to the get call like so: get :app_config, use_route: :config, then it works for some reason, though I don't understand why? But when appending that, I get the following deprecation error:

DEPRECATION WARNING: Passing the `use_route` option in functional tests are deprecated. Support for this option in the `process` method (and the related `get`, `head`, `post`, `patch`, `put` and `delete` helpers) will be removed in the next version without replacement.
Functional tests are essentially unit tests for controllers and they should not require knowledge to how the application's routes are configured. Instead, you should explicitly pass the appropiate params to the `process` method. 
Previously the engines guide also contained an incorrect example that recommended using this option to test an engine's controllers within the dummy application. 
That recommendation was incorrect and has since been corrected. 
Instead, you should override the `@routes` variable in the test case with `Foo::Engine.routes`. See the updated engines guide for details.

Here is my controller:

# config_controller.rb
class Api::V4::ConfigController < Api::V4::BaseController

  def app_config
    render json: Api::V6::Config.app_config, root: false
  end

end

And routes:

# routes.rb
MyApp::Application.routes.draw do
  constraints subdomain: /\Awww\b/ do
    namespace :api, defaults: {format: 'json'} do
      get 'app_config' => 'config#app_config'
    end
  end
end

Solution

  • Use a request spec instead of a controller spec:

    describe "Api V4 Configuration", type: :request do
      let(:json) { JSON.parse(response.body) }
      subject { response }
    
      describe 'GET app_config' do
        before { get "/api/v4/app_config" }
        it { should be_successful } 
        it "has the correct contents" do
          expect(json).to include(foo: "bar")
        end
      end
    end
    

    One of biggest changes with Rails 5 was the depreciation of ActionController::TestCase (which RSpec controller specs wrap) in favor of integration tests. Thus using request specs is a more future proof solution - using less abstraction also means that your specs will cover routing properly as well.

    Also you don't seem to be nesting your routes properly:

    # routes.rb
    MyApp::Application.routes.draw do
      constraints subdomain: /\Awww\b/ do
        namespace :api, defaults: {format: 'json'} do
          namespace :v4 do
            get 'app_config' => 'config#app_config'
          end
        end
      end
    end
    

    See: