Devise: Rspec 3.5.1/Rails 5/Devise 4.2.0: ArgumentError in test that used to work

Created on 13 Jul 2016  路  4Comments  路  Source: heartcombo/devise

Rails 5 is now out and I am upgrading my basic login/CRUD app to use the new framework. Things were pretty straightforward, except for one rspec test that is failing. I tried stepping through the code with pry, but finding the problem was beyond me. Any pointers would be appreciated.

This is the error:

Failures:

  1) User email rejects email addresses with improper format
     Failure/Error: expect(user).not_to be_valid

   ArgumentError:
     wrong number of arguments (given 0, expected 1)
     # ./spec/models/problem_test_user_spec.rb:13:in `block (4 levels) in <top (required)>'
     # ./spec/models/problem_test_user_spec.rb:11:in `each'
     # ./spec/models/problem_test_user_spec.rb:11:in `block (3 levels) in <top (required)>'

Here is the pared-down test:

     1  require 'rails_helper'
     2  require 'devise'
     3  
     4  RSpec.describe User, type: :model do
     5    let (:user) { FactoryGirl.create :user }
     6  
     7    describe "email" do
     8      it "rejects email addresses with improper format" do
     9        invalid_addresses = %w[ user@example,com user_at_foo.org user.name@example. ]
    10  
    11        invalid_addresses.each do |addr|
    12          user.email = addr
    13          expect(user).not_to be_valid
    14        end
    15      end
    16    end
    17  end

Here is my model:

    class User < ApplicationRecord
      devise :database_authenticatable, :validatable

      validates :email, length: { maximum: 254 } # max length per RFC 3696, errata ID 1690
    end

Here is my factory:

    FactoryGirl.define do
      factory :user do
        name      { Faker::Name.name }
        tone_name { "#{Faker::Company.name} Tone" }
        email     { Faker::Internet.email }
        password              SOME_PASSWORD
        password_confirmation SOME_PASSWORD
        admin      false
      end
    end

While the test was failing, the app under Rails 5 was running fine in my development environment, but it allowed an invalid password of the form name@example,com where the Rails 4.2 version both passes the rspec test and catches this invalid password with an error.

While researching this, I learned that Devise changed the default email_regexp to a less strict expression in v4.1. My test is actually failing because an email address that used to be identified as illegal now is accepted. The ArgumentError is not in my code. I believe it is an error either in Devise or in Rspec. If I put a binding.pry in my test, then a simple #inspect on my model causes the following:

        12:       invalid_addresses.each do |addr|
        13:         user.email = addr
        14:         binding.pry
     => 15:         expect(user).not_to be_valid
        16:       end
        17:     end
        18:   end
        19: end

    [1] pry(#<RSpec::ExampleGroups::User::Email>)> user.inspect
    ArgumentError: wrong number of arguments (given 0, expected 1)
    from /Users/tarsa/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/database_authenticatable.rb:146:in `password_digest'

The Devise code may not be at fault, I suspect some interaction with ActiveRecord is going on, but I am at the limit of my debugging skills at this point.

I can workaround the problem by configuring Devise to use the 4.0.3 version of email_regexp. If all the password strings are recognized as invalid, then the bug, wherever it is, does not get tickled.

I am still interested in a root cause, but I am no longer blocked.

Needs more info

Most helpful comment

@gltarsa Thanks!

I dug through the whole stack trace (using RSpec -b switch) and found the root cause here:

$ bundle exec rspec spec/models/problem_test_user_spec.rb -b
F

Failures:

  1) User email rejects email addresses with improper format
     Failure/Error: expect(user).not_to be_valid

     ArgumentError:
       wrong number of arguments (given 0, expected 1)
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/database_authenticatable.rb:146:in `password_digest'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `block in serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `each'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0/lib/active_record/serialization.rb:17:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/authenticatable.rb:114:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/authenticatable.rb:120:in `inspect'

The users table has a password_digest column that is conflicting with Devise's password_digest(password) method, and Rails serializable_hash is trying to call the method as an attribute instead (thus making the (given 0, expected 1) bad method invocation).

This is happening now because Devise 4.2.0 now uses serializable_hash on inspect to avoid serializing sensitive data, so when the RSpec matcher tries to do user.inspect it fails. But even without upgrading Devise, calling user.as_json / user.serializable_hash on your app fails because of the attribute/method conflict.

I think in your case the best option is to remove the password_digest column from the users table as Devise uses the encrypted_password instead.

Hope this helps - I'm closing this issue as it isn't an issue with Devise, but let me know if you have any other questions.

All 4 comments

Can you please provide a sample application that reproduces the error?

I stripped my app down to the bare essentials. Clone this branch, install postgresql, run bundle install, rake db:setup and then rspec should show the problem described in my issue report.

https://github.com/gltarsa/Motleytones/tree/minimal-test-app-for-devise-rails5-problem

Thanks!

@gltarsa Thanks!

I dug through the whole stack trace (using RSpec -b switch) and found the root cause here:

$ bundle exec rspec spec/models/problem_test_user_spec.rb -b
F

Failures:

  1) User email rejects email addresses with improper format
     Failure/Error: expect(user).not_to be_valid

     ArgumentError:
       wrong number of arguments (given 0, expected 1)
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/database_authenticatable.rb:146:in `password_digest'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `block in serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `each'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0/lib/active_model/serialization.rb:135:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0/lib/active_record/serialization.rb:17:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/authenticatable.rb:114:in `serializable_hash'
     # /opt/rubies/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.2.0/lib/devise/models/authenticatable.rb:120:in `inspect'

The users table has a password_digest column that is conflicting with Devise's password_digest(password) method, and Rails serializable_hash is trying to call the method as an attribute instead (thus making the (given 0, expected 1) bad method invocation).

This is happening now because Devise 4.2.0 now uses serializable_hash on inspect to avoid serializing sensitive data, so when the RSpec matcher tries to do user.inspect it fails. But even without upgrading Devise, calling user.as_json / user.serializable_hash on your app fails because of the attribute/method conflict.

I think in your case the best option is to remove the password_digest column from the users table as Devise uses the encrypted_password instead.

Hope this helps - I'm closing this issue as it isn't an issue with Devise, but let me know if you have any other questions.

Thank you, this was illuminating in a couple of dimensions. I was on a complete tangent for this. Much obliged.

Was this page helpful?
0 / 5 - 0 ratings