Reputation: 53
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
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
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