devise
devise copied to clipboard
Rails 8: test helpers do not work
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_loadedbefore thesign_incall.
Mine worked after adding
devise_for :users in the route file.
Note: Mine was graphql application so, I did not have this added initially.
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
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
This also worked for me in my test_helper.rb. Thanks for sharing, I never would have grokked this fix!
Looks like it is influence not only on tests, but also registration is not working in application due to same error
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?
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
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 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
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