devise icon indicating copy to clipboard operation
devise copied to clipboard

Rails 8: test helpers do not work

Open jeromedalbert opened this issue 1 year ago • 1 comments

Environment

  • Ruby 3.3.2
  • Rails main (8.0.0.alpha)
  • Devise 4.9.4

Steps to reproduce

Run the following bash commands (tested on macOS):

rails new myapp --main
cd myapp
bundle add devise
rails generate devise:install
rails generate devise User
rm test/fixtures/users.yml
rails db:migrate

sed -i '' 's/end$/  root "hello#index"\nend/' config/routes.rb

echo 'class HelloController < ApplicationController
  def index
    authenticate_user!
    render plain: "Hello"
  end
end' > app/controllers/hello_controller.rb

echo 'require "test_helper"
class HelloControllerTest < ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers
  def test_index
    sign_in User.new
    get "/"
    assert_response :success
  end
end' > test/integration/hello_controller_test.rb

BACKTRACE=1 rails test test/integration/hello_controller_test.rb

Current behavior

I get the following error:

Error:
HelloControllerTest#test_index:
RuntimeError: Could not find a valid mapping for #<User id: nil, email: [FILTERED], created_at: nil, updated_at: nil>
    devise (4.9.4) lib/devise/mapping.rb:46:in `find_scope!'
    devise (4.9.4) lib/devise/test/integration_helpers.rb:38:in `sign_in'
    test/integration/hello_controller_test.rb:5:in `test_index'
    minitest (5.24.1) lib/minitest/test.rb:95:in `block (3 levels) in run'
    minitest (5.24.1) lib/minitest/test.rb:192:in `capture_exceptions'
    minitest (5.24.1) lib/minitest/test.rb:90:in `block (2 levels) in run'
    minitest (5.24.1) lib/minitest.rb:368:in `time_it'
    minitest (5.24.1) lib/minitest/test.rb:89:in `block in run'
    minitest (5.24.1) lib/minitest.rb:467:in `on_signal'
    minitest (5.24.1) lib/minitest/test.rb:240:in `with_info_handler'
    minitest (5.24.1) lib/minitest/test.rb:88:in `run'
    rails (e13b251ae078) activesupport/lib/active_support/executor/test_helper.rb:5:in `block in run'
    rails (e13b251ae078) activesupport/lib/active_support/execution_wrapper.rb:104:in `perform'
    rails (e13b251ae078) activesupport/lib/active_support/executor/test_helper.rb:5:in `run'
    minitest (5.24.1) lib/minitest.rb:1200:in `run_one_method'
    minitest (5.24.1) lib/minitest.rb:433:in `run_one_method'
    minitest (5.24.1) lib/minitest.rb:420:in `block (2 levels) in run'
    minitest (5.24.1) lib/minitest.rb:419:in `each'
    minitest (5.24.1) lib/minitest.rb:419:in `block in run'
    minitest (5.24.1) lib/minitest.rb:467:in `on_signal'
    minitest (5.24.1) lib/minitest.rb:454:in `with_info_handler'
    minitest (5.24.1) lib/minitest.rb:418:in `run'
    rails (e13b251ae078) railties/lib/rails/test_unit/line_filtering.rb:10:in `run'
    minitest (5.24.1) lib/minitest.rb:332:in `block in __run'
    minitest (5.24.1) lib/minitest.rb:332:in `map'
    minitest (5.24.1) lib/minitest.rb:332:in `__run'
    minitest (5.24.1) lib/minitest.rb:288:in `run'
    minitest (5.24.1) lib/minitest.rb:86:in `block in autorun'

Expected behavior

Test should pass:

.

Finished in 0.106683s, 9.3736 runs/s, 9.3736 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Additional information

  • This issue happened after a new attempt at deferred route drawing got merged. It is a similar problem as #5694 (see that issue for more details) except that the repro steps are different: here I wasn't able to come up with a failing single file repro script, so instead I laid out steps for a full-blown repro app.
  • A workaround is to use Rails.application.reload_routes_unless_loaded before the sign_in call.

jeromedalbert avatar Aug 10 '24 02:08 jeromedalbert

Mine worked after adding devise_for :users in the route file. Note: Mine was graphql application so, I did not have this added initially.

bugloper avatar Sep 05 '24 16:09 bugloper

Temp workaround:

RSpec.configure do |config|
  …
  # TODO Remove when Devise fixes https://github.com/heartcombo/devise/issues/5705
  config.before(:each, type: :controller) do
    Rails.application.reload_routes_unless_loaded
  end
end

iRonin avatar Oct 28 '24 12:10 iRonin

In my case, the tests that were failing were the ones indirectly involving Devise::Mailer. This mailer actually gets invoked pretty often, because creating a user in a test (e.g. via FactoryBot) will try to send a confirmation email by default as a side-effect.

Anyway, here is the workaround I added to my test_helper.rb:

ActiveSupport.on_load(:action_mailer) do
  Rails.application.reload_routes_unless_loaded
end

mattbrictson avatar Oct 28 '24 18:10 mattbrictson

This also worked for me in my test_helper.rb. Thanks for sharing, I never would have grokked this fix!

epugh avatar Nov 10 '24 11:11 epugh

Looks like it is influence not only on tests, but also registration is not working in application due to same error

le0pard avatar Nov 18 '24 09:11 le0pard

Thanks @jeromedalbert for reporting this and working on a fix. I've fixed it in https://github.com/heartcombo/devise/pull/5728. I don't think it's a perfect solution but that's the only one I could think of without massive changes to how mapping works in Devise.

Could everyone test it and report back if there's any issue with such approach?

nashby avatar Nov 24 '24 15:11 nashby

Thank you for the fix! This worked for me. Here's a small OSS repo showing it in case it's helpful for reference: https://github.com/CodingItWrong/slapdash/pull/324

CodingItWrong avatar Dec 01 '24 12:12 CodingItWrong

As for me, I already had the devise_for :users, the error is coming from Devise::Mapping.find_scope!(resource), the workaround was to replace the devise helper:

- sign_in user
+ login_as user

JuanVqz avatar Feb 10 '25 05:02 JuanVqz

@JuanVqz Since #5728 has not been released, I think we need to refer to master or continue using the workaround.


I'm using Request spec and the following code worked.

RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.before(:each, type: :request) do
    Rails.application.reload_routes_unless_loaded
  end
end

yktakaha4 avatar Feb 14 '25 14:02 yktakaha4

I resolved the same issue by adding this patch in config/initializers/devise_rails8_patch.rb: https://github.com/heartcombo/devise/pull/5728#issuecomment-2539418211

  require 'devise'
  Devise # make sure it's already loaded

  module Devise
    def self.mappings
      # Starting from Rails 8.0, routes are lazy-loaded by default in test and development environments.
      # However, Devise's mappings are built during the routes loading phase.
      # To ensure it works correctly, we need to load the routes first before accessing @@mappings.
      Rails.application.try(:reload_routes_unless_loaded)
      @@mappings
    end
  end

soniillusions avatar Mar 29 '25 20:03 soniillusions