coreyward
coreyward

Reputation: 80041

No route matches in functional/controller test

I have the following controller test using Minitest::Rails and Rails 4. When I run it, I get an error: ActionController::UrlGenerationError: No route matches {:action=>"/", :controller=>"foo"} error, despite it being defined.

The whole point of this is to test methods that are on ApplicationController (FooController exists only in this test and is not a placeholder for the question).

class FooController < ApplicationController
  def index
    render nothing: true
  end
end

class FooControllerTest < ActionController::TestCase
  it 'does something' do
    with_routing do |set|
      set.draw do
        root to: 'foo#index', via: :get
      end

      root_path.must_equal '/' #=> 👍
      get(root_path).must_be true #=> No route matches error
    end
  end
end

There a number of similar questions on StackOverflow and elsewhere, but they all refer to the issue of route segments being left out (e.g. no ID specified on a PUT). This is a simple GET with no params, however.

I get the same result if the route is assembled differently, so I don't think it's the root_path bit doing it (e.g. controller :foo { get 'test/index' => :index }).

Upvotes: 1

Views: 1414

Answers (4)

coreyward
coreyward

Reputation: 80041

The solution to this problem was much simpler than expected:

# change this:
get(root_path)

# to this
get(:index)

The with_routing method works fine to define the path in this context.

Upvotes: 0

Saiqul Haq
Saiqul Haq

Reputation: 2397

if you check the source of ActionController::TestCase#get method, it expects action name, e.g. :index, :create, 'edit, 'create'

if you pass root_path on #get method, absolutely it will raise error, because root_path method returns '/'. I just checked to add :/ method to FooController

class FooController
  def index
  end

  def /
  end
end

Rails.application.routes.draw do
  root 'foo#index'
  get 'foobar' => 'foo#/'
end

when I was visiting http://localhost:3000/foobar, rails gave me AbstractController::ActionNotFound (The action '/' could not be found for FooController): respond

I think '/' is not permitted action on rails, I don't do research further, because I think it's very reasonable.

You may write assert_routing '/', controller: "foo", action: "index" for current test, then you can write integration test to check root_path and other features.

Following are the source code of some methods I've talked above: (I'm using rails version 4.2.3 to test this interesting issue)

action_controller/test_case.rb

  # Simulate a GET request with the given parameters.
  #
  # - +action+: The controller action to call.
  # - +parameters+: The HTTP parameters that you want to pass. This may
  #   be +nil+, a hash, or a string that is appropriately encoded
  #   (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
  #
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
  # +post+, +patch+, +put+, +delete+, and +head+.
  #
  # Note that the request method is not verified. The different methods are
  # available to make the tests more expressive.
  def get(action, *args)
    process(action, "GET", *args)
  end


  # Simulate a HTTP request to +action+ by specifying request method,
  # parameters and set/volley the response.
  #
  # - +action+: The controller action to call.
  # - +http_method+: Request method used to send the http request. Possible values
  #   are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
  # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
  #   string that is appropriately encoded (+application/x-www-form-urlencoded+
  #   or +multipart/form-data+).
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
  #
  # Example calling +create+ action and sending two params:
  #
  #   process :create, 'POST', user: { name: 'Gaurish Sharma', email: '[email protected]' }
  #
  # Example sending parameters, +nil+ session and setting a flash message:
  #
  #   process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
  #
  # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
  # prefer using #get, #post, #patch, #put, #delete and #head methods
  # respectively which will make tests more expressive.
  #
  # Note that the request method is not verified.
  def process(action, http_method = 'GET', *args)
    # ..... 
  end

Upvotes: 2

Anand Soni
Anand Soni

Reputation: 5110

I did some search on what information you have provided. I found an issue open in rspec-rails gem. However gem doesn't matter here but fundamentally they said its context problem. When you call with_routing it doesn't executed in correct context, so gives error of No Route matches.

To resolve issue, I tried locally with different solution. Here is what I have tried

require 'rails_helper'

RSpec.describe FooController, type: :controller do
  it 'does something' do

    Rails.application.routes.draw do
      root to: 'foo#index', via: :get
    end

   expect(get: root_path).to route_to("foo#index")

  end
end

In above code, the major problem is it overwrite existing routes. But we can reproduce routes with Rails.application.reload_routes! method.

I hope this helps to you!!

UPDTATE

I tried to understand your last comment and dig into get method. When we call get method it takes argument of action of controller for which we are doing test. In our case when we do get(root_path) it tries to find foo#/ which is not exists and hence gives no route matches error.

as our main goal is to check root_path routes are generated correctly, we need to use method assert_routing to check it. Here is how I test and it works assert_routing root_path , controller: "foo", action: "index"

Full code :

require 'test_helper'

class FooControllerTest < ActionController::TestCase
  it 'does something' do
    with_routing do |set|
      set.draw do
        root to: 'foo#index', via: :get
      end

      root_path.must_equal '/' #=> true
      assert_routing root_path , controller: "foo", action: "index" #=> true
      get :index
      response.body.must_equal ""
    end
  end
end

I read things from official document : http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html

Upvotes: 2

MilesStanfield
MilesStanfield

Reputation: 4639

The whole point of this is to test behavior inherited by the ApplicationController.

There is no behavior in your question related to ApplicationController other than the fact that FooController inherits from ApplicationController. And since there's no other behavior to test here in FooController related to something from the Application Controller ...

You can test this

class FooController < ApplicationController
  def index
    render nothing: true
  end
end

with this

describe FooController, type: :controller do
  describe '#index' do
    it 'does not render a template' do
      get :index
      expect(response).to render_template(nil)
    end
  end
end

Upvotes: 0

Related Questions