user3701329
user3701329

Reputation: 53

Rspec be_valid fails on a valid test

I'm learning TDD with Rails 4 and rspec. I've made some test cases for my user model to check the password lengths. I have two tests so far that checks whether a user input a password that was too short and one where the password is between 6 - 10 characters.

So far, the "password is too short" test passes:

it "validation says password too short if password is less than 6 characters" do
  short_password = User.create(email: "[email protected]", password: "12345")
  expect(short_password).not_to be_valid
end  

However, on the test where I do have a valid password, it fails:

it "validation allows passwords larger than 6 and less than 10" do
  good_password = User.create(email: "[email protected]", password: "blahblah")
  expect(good_password).to be_valid
end

And I get this error:

Failure/Error: expect(good_password).to be_valid
   expected #<User id: 1, email: "[email protected]", 
   created_at: "2014-06-21 02:43:42", updated_at: "2014-06-21 02:43:42",
   password_digest: nil, password: nil, password_hash: "$2a$10$7u0xdDEcc6KJcAi32LBW7uzV9n7xYbfOhZWdcOnU5Cdm...",
   password_salt: "$2a$10$7u0xdDEcc6KJcAi32LBW7u"> to be valid, 
   but got errors: Password can't be blank, Password is too short (minimum is 6 characters)
 # ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'

Here's my model code:

class User < ActiveRecord::Base

  has_many :pets, dependent: :destroy
  accepts_nested_attributes_for :pets, :allow_destroy => true
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
  uniqueness: true

  validates :password, presence: true, :length => 6..10, :confirmation => true

  #callbacks
  before_save :encrypt_password
  after_save :clear_password

  #method to authenticate the user and password
  def self.authenticate(email, password)
    user = find_by_email(email)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end

  #method to encrypt password
  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end

  #clears password
  def clear_password
    self.password = nil
  end
end

I'm confused on why the password is nil when I create the test object.

Upvotes: 3

Views: 11456

Answers (2)

Allison
Allison

Reputation: 2343

You have a password presence requirement on your model, but then you have an after_save hook that nilifies the password and puts the record into an invalid state. The first test passes because your records are always being put into an invalid state by the after_save hook. You need to rethink how you're handling password storage; once you resolve that, here are some code samples to help give you some ways to test this:

# Set up a :user factory in spec/factories.rb; it should look something like:
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "tester+#{n}@gmail.com" }
    password         { SecureRandom.hex(6) }
  end
end 

# In your spec:
let(:user) { create :user, password: password }

context 'password' do
  context 'length < 6' do
    let(:password) { '12345' } 


    it { expect(user).not_to be_valid }
    it { user.errors.message[:password]).to include('something') }
  end 

  context 'length >= 6' do
    context 'length < 10' do
      let(:password) { 'blahblah' }

      it { expect(user).to be_valid }
    end

    context 'length >= 10' do
      let(:password) { 'blahblahblah' }

      it { expect(user).not_to be_valid }
    end
  end
end

You can also use shoulda matchers:

it { should_not allow_value('12345').for(:password) }
it { should allow_value('12345blah').for(:password) }

Upvotes: 2

David Miani
David Miani

Reputation: 14678

The most likely problem is the password field is not mass assignable. That is why password is nil in the output message.

Try this instead:

it "validation allows passwords larger than 6 and less than 10" do
  good_password = User.create(email: "[email protected]")
  good_password.password = "blahblah"
  expect(good_password).to be_valid
end

Note that your first test is passing accidentally - it has the same problem as the second test (password isn't being assigned). This means you aren't actually testing that the password is rejected when less than 6 characters atm.

See this article on mass assignment for more details.

EDIT: Leo Correa's comment may suggest this may not be the case for you. Posting your model code would help...

Upvotes: 0

Related Questions