ruby-on-railsdeviseruby-on-rails-5integration-testingwarden

Rails 5 Integration test fails with NoMethodError: undefined method `[]=' for nil:NilClass when using Devise helper sign_in


I'm writing an integration test for Rails v5.1 using built-in Minitest.

Here's the integration test class:

require 'test_helper'

class PuppiesEndpointsTest < ActionDispatch::IntegrationTest

    include Devise::Test::IntegrationHelpers

    test "DELETE puppy" do
        marty = people(:marty)

        sign_in(marty)

        # delete puppies_delete_path(marty.puppies.first.id)
        # delete `/api/v1/puppies/destroy/${marty.puppies.first.id}.json`
        # delete puppies_path(marty.puppies.first.id)
        delete '/api/v1/puppies/destroy/6666.json'
        assert_response :success
    end

end

All of the routes above, including the ones that are commented out, result in the same cryptic error:

Error:
PuppiesEndpointsTest#test_DELETE_puppy:
NoMethodError: undefined method `[]=' for nil:NilClass
    test/integration/puppies_endpoints_test.rb:17:in `block in <class:PuppiesEndpointsTest>'


bin/rails test test/integration/puppies_endpoints_test.rb:7

It doesn't give a stack trace or any other information to diagnose what the hell it's talking about. I used byebug to debug the marty variable right before that delete line that's throwing the error. It shows the expected puppies array of associated (fixture) records.

I've also placed a byebug at the very top of the controller action and this error fails the test before it reaches that byebug, so I think that pretty much rules out anything in the action code.

Here's the relevant chunk of what I see when I run rake routes:

                       PATCH      /api/v1/puppies/edit/:id(.:format)        puppies#update
                       DELETE     /api/v1/puppies/destroy/:id(.:format)     puppies#destroy
        puppies_create POST       /api/v1/puppies/create(.:format)          puppies#create

Here's what is actually in the my routes file:

  scope '/api' do
    scope '/v1' do
      devise_for :people

      patch 'puppies/edit/:id' => 'puppies#update'
      delete 'puppies/destroy/:id' => 'puppies#destroy'#, as: 'puppies_delete'
      post 'puppies/create' => 'puppies#create'
      ...

I'm completely stumped as to what/why I'm getting this error. The actual code is working completely as expected.

My hunch is that maybe there's a missing config variable that's not getting set for the test environment (I use dotenv gem), but I have no idea how to track that down if the error won't give me any context whatsoever.

UPDATE

I have isolated this problem to using the Devise helper sign_in method. When I remove this method call, the problem goes away.

Here's the problematic test class:

require 'test_helper'

class PuppiesEndpointsTest < ActionDispatch::IntegrationTest

    include Devise::Test::IntegrationHelpers

    test "do stuff" do
       ...

app/controllers/api_controller.rb:

class ApiController < ActionController::API
end

Maybe sign_in does not work for testing controllers that do not inherit from ActionController::Base

I changed the controller to inherit from ActionController::Base and nothing changed. I still can't use sign_in without getting that error, but it works find if I "manually" post a request to the sign_in endpoint.

UPDATE 2 I found this Devise issue which sounds related to my problem: https://github.com/plataformatec/devise/issues/2065


Solution

  • Looks like I found the issue. Apparently, in rails-api mode, the ActionDispatch::Cookies and ActionDispatch::Session::CookieStore middlewares are inserted in the end of the middleware stack, which doesn't occur in normal Rails mode.

    Due to this, those middlewares are included after Warden::Manager which messes up something in request specs.

    Try to set in test.rb

    Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Cookies
    Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Session::CookieStore