raquelhortab
raquelhortab

Reputation: 516

Rails controller test cannot find route

I am using Ruby 1.9.3 with Rails 3.2 (old, I know).

I am having trouble with one controller test. The routes work just fine, the only problem is the test.

The test is placed in project_root/test/functional/api/ubiquo/work_hours_controller_test.rb

And is defined like this:

# encoding: utf-8
require 'test_helper'
class Api::Ubiquo::WorkHoursControllerTest < ActionController::TestCase

  setup :prepare

  test "check_already_logged_in_ubiquo: error if not logged in" do
    get :check_already_logged_in_ubiquo, format: :json
    assert_response :unauthorized
  end

  def prepare
    # I tried both specifying the controller like this and also letting Rails do it
    @controller = Api::Ubiquo::WorkHoursController.new
  end
  
end

The controller is placed in project_root/app/controllers/api/ubiquo/work_hours_controller.rb and is defined like this. (I simplified it to debug)

class Api::Ubiquo::WorkHoursController < Api::Ubiquo::BaseController
  def check_already_logged_in_ubiquo
    respond_to do |format|
      format.json { render json: { message: 'Not logged in' }, status: :unauthorized }
    end
  end
end

rellevant part of routes.rb:

scope :path => "api", :format => 'json' do
  get 'ubiquo/check_already_logged_in_ubiquo', to: 'api/ubiquo/work_hours#check_already_logged_in_ubiquo'

  # I also tried this:
  # namespace :ubiquo do
  #    get 'check_already_logged_in_ubiquo', to: 'api/ubiquo/work_hours#check_already_logged_in_ubiquo'
  # end
end

rake routes returns the correct route (which works when running the app)

ubiquo_check_already_logged_in_ubiquo GET    /api/ubiquo/check_already_logged_in_ubiquo(.:format)             api/ubiquo/work_hours#check_already_logged_in_ubiquo {:format=>"json"}

The error I get is:

ActionController::RoutingError: No route matches {:format=>:json, :controller=>"api/ubiquo/work_hours", :action=>"check_already_logged_in_ubiquo"}

I have also tried defining the test as class CustomWorkHoursControllerTest < ActionController::TestCase (without the modules Api::Ubiquo) and then the error does not fire but request just returns 406. In no case the debugger reaches the controller.

All other controller tests are working just fine, including those in /api, just not those in /api/ubiquo.

I really don't know what else to try.

Update I saw that test_helper was doing somethings I was not aware of, I removed those and now I get

Expected response to be a <:unauthorized>, but was <406>

still not entering the controller.

Some info I get while debugging (which looks just fine): in the request @env:

action_dispatch.request.parameters => {ActiveSupport::HashWithIndifferentAccess} {action: check_already_logged_in_ubiquo, controller: api/ubiquo/work_hours, format: json}

Upvotes: 0

Views: 50

Answers (2)

raquelhortab
raquelhortab

Reputation: 516

It turned out it was all due to the controller in question inheriting from another controller with a before_filter that renders in formats :js, :xml, :atom, :rss, or :html when not logged in before it could ever reach my action. And since :json is not among the formats in the filter, it fired :not_acceptable 406.

It did work when I called it normally because I was logged in...

Upvotes: 0

max
max

Reputation: 102249

This test is the old and depreachiated functional style tests. Don't use them.

What they do is basically just instanciate an instance of your controller with a faked request and then call the method on the controller. Controllers are Rack apps and cannot function without it which is why @controller = Api::Ubiquo::WorkHoursController.new breaks.

The router isn't even actually used. The test just tries to find something vaguely correct in the route set by searching by controller, action and format which is extremely error prone.

Even back in Rails 3.0.2 you have ActionDispatch::IntegrationTest which can be used instead which is a future proof approach.

# encoding: utf-8
require 'test_helper'

module Api
  module Ubiquo
    class WorkHoursControllerTest < ActionDispatch::IntegrationTest
      test "check_already_logged_in_ubiquo: error if not logged in" do
        get 'api/ubiquo/check_already_logged_in_ubiquo', format: :json
        assert_response :unauthorized
      end
    end 
  end
end

You can declare the routes in a less clunky way with:

namespace :api, :format => 'json' do
  namespace :ubiquo do
    resources :work_hours, :only => [] do
      get :check_already_logged_in_ubiquo
    end
  end
end

namespace :api is just shorthand for scope '/api', module: :api.

You're also misusing the scope resolution operator (::) for declaring nested classes. This is not an equivilent short hand syntax. It doesn't actually set the module nesting correctly and leads to wierd constant lookups as well as issues with the classic autoloader since it (ab)uses constant_missing.

Declare namespaces explicitly.

I have also tried defining the test as class CustomWorkHoursControllerTest < ActionController::TestCase (without the modules Api::Ubiquo) and then the error does not fire but request just returns 406. In no case the debugger reaches the controller.

ActionController::TestCase uses a set of heuristics to guess the name of the class under test based on the name of the test class including the modules it is nested in. While nothing here is obviously wrong you're this can be overridden by using the tests method.

require 'test_helper'
class Api::Ubiquo::WorkHoursControllerTest < ActionController::TestCase

  tests Foo::Bar:BazController

  test "check_already_logged_in_ubiquo: error if not logged in" do
    get :check_already_logged_in_ubiquo, format: :json
    assert_response :unauthorized
  end
end

Upvotes: 1

Related Questions