Devise in test environment: mapping.to.serialize_from_session gives error "wrong number of arguments (given 10, expected 2)"
Environment
- Ruby [3.3.6]
- Rails [8.0.0]
- Devise [4.9.4]
Current behavior
I just updated my app to rails 8 with the newest version of Devise and hotwire turbo. When running tests with Capybara/Rspec I'm seeing certain form submissions fail with a 500 error from inside of Devise. This behavior seems to be only in the test environment.
lib/devise.rb:496
# A method used internally to complete the setup of warden manager after routes are loaded.
# See lib/devise/rails/routes.rb - ActionDispatch::Routing::RouteSet#finalize_with_devise!
def self.configure_warden! #:nodoc:
@@warden_configured ||= begin
warden_config.failure_app = Devise::Delegator.new
warden_config.default_scope = Devise.default_scope
warden_config.intercept_401 = false
Devise.mappings.each_value do |mapping|
warden_config.scope_defaults mapping.name, strategies: mapping.strategies
warden_config.serialize_into_session(mapping.name) do |record|
mapping.to.serialize_into_session(record)
end
warden_config.serialize_from_session(mapping.name) do |args|
mapping.to.serialize_from_session(*args)
end
end
@@warden_config_blocks.map { |block| block.call Devise.warden_config }
true
end
end
When mapping.to.serialize_from_session(*args) is called I get the error "wrong number of arguments (given 10, expected 2)".
I have not been able to find a pattern between tests that pass and tests that cause this behavior except that I can see the data is different.
On a passing test args looks like this:
[1] pry(#<Warden::SessionSerializer>)> args
=> [[1], "$2a$04$yPg9CBmwVeIZc3VYqdmL3O"]
But on a failing test args looks like this:
[1] pry(#<Warden::SessionSerializer>)> args
=> {"id"=>27,
"email"=>"[email protected]",
"created_at"=>"2000-01-01T05:00:03.000Z",
"updated_at"=>"2000-01-01T05:00:19.000Z",
"client_id"=>18,
"role"=>"coordinator",
"authentication_token"=>"pxcFTApxeyCo1evMNd9X",
"first_name"=>"User12",
"last_name"=>"Name",
"analytics_enabled"=>false}
What could be causing this? Looks like a hash composed of a full user record instead of just an id and token.
I understand there have been issues with Devise.mapping with the newest version of rails, I've tried adding in the recommended initializer file from here, but it had no effect:
https://github.com/heartcombo/devise/pull/5728#issuecomment-2539418211
It seems that while the test is running, devise hasn't configured its warden stuff, yet. That should be done while finalizing the route set: https://github.com/heartcombo/devise/blob/fec67f98f26fcd9a79072e4581b1bd40d0c7fa1d/lib/devise/rails/routes.rb#L19
Obviously the patch from https://github.com/heartcombo/devise/pull/5728 isn't enough to make it work in system tests.
If you want to use a quick workaround do something like that in your spec/spec_helper.rb:
RSpec.configure do |config|
config.before(:each, type: :system) do
Rails.application.try(:reload_routes_unless_loaded)
end
end
Also hurt by this.
What's funny is that when I run the entire test suite it's all green. But when I do rspec spec/controllers/ANYTHING, the first controller test fails with that deserialization exception.
The workaround in previous comment works:
config.before(:each, type: :controller) do
Rails.application.try(:reload_routes_unless_loaded)
end
Rails 8.0.1, Devise 4.9.4, Ruby 3.4.1.
wow... spent hours troubleshooting that one.
indeed solves the issue.
Have some issue with capibara
AI has just put this in the code I'm working on (for the User model) and brought this issue to my attention
# Override serialize_from_session to fix Rails 8/Devise 4.9.4 compatibility
# See: https://github.com/heartcombo/devise/issues/5752
def self.serialize_from_session(key, salt = nil)
# Handle both old format [id, salt] and new format (full record hash)
if key.is_a?(Hash)
# New format: full record hash from Rails 8
record_id = key["id"] || key[:id]
return nil unless record_id
find_by(id: record_id)
elsif key.is_a?(Array) && key.length >= 1
# Old format: [id] or [id, salt]
record_id = key.first
return nil unless record_id
find_by(id: record_id)
else
# Single ID value
find_by(id: key)
end
end
I'm yet to look into this but HTH
This is the response that works. Thanks @aiomaster for taking the time to post this.
It seems that while the test is running, devise hasn't configured its warden stuff, yet. That should be done while finalizing the route set:
devise/lib/devise/rails/routes.rb
Line 19 in fec67f9
Devise.configure_warden! Obviously the patch from #5728 isn't enough to make it work in system tests.
If you want to use a quick workaround do something like that in your
spec/spec_helper.rb:RSpec.configure do |config| config.before(:each, type: :system) do Rails.application.try(:reload_routes_unless_loaded) end end
As this is only a problem for the first spec, just running this before the suite also works and avoids any race conditions with other before blocks (which I ran into 😅)
@aiomaster thanks so much for this fix, had flakey tests for a year or so 🥲
RSpec.configure do |config|
config.before(:suite) do
Rails.application.try(:reload_routes_unless_loaded)
end
end