I am testing my account model with shoulda-matchers using a entity created with FactoryGirl.
The code of both files look like this:
TEST FILE
require 'spec_helper'
describe Account do
before { #account = FactoryGirl.build(:account) }
subject { #account }
it { should validate_presence_of(:email) }
it { should validate_presence_of(:password) }
it { should validate_presence_of(:password_confirmation).
with_message(#account.password_confirmation+' '+#account.password) }
it { should allow_value('example#domain.com').for(:email) }
it { should be_valid }
end
FACTORY
FactoryGirl.define do
factory :account do
email { FFaker::Internet.email }
password "12345678"
password_confirmation "12345678"
end
end
My error is the following:
1) Account should require password_confirmation to be set
Failure/Error: it { should validate_presence_of(:password_confirmation).
Expected errors to include "12345678 12345678" when password_confirmation is set to nil, got errors: ["password_confirmation can't be blank (nil)"]
# ./spec/models/account_spec.rb:9:in `block (2 levels) in <top (required)>'
rspec ./spec/models/account_spec.rb:9 # Account should require password_confirmation to be set
I am using devise which should check for password confirmation. I know its probably because of something stupid, but I really can't figure out what's wrong with it.
Devise, doesn't validate the presence of password_confirmation it just validates the confirmation of password see Validatable line 34: https://github.com/plataformatec/devise/blob/v3.5.3/lib/devise/models/validatable.rb#L34
validates_confirmation_of :password, if: :password_required?
EDIT: You could also see that there are no validators on password_confirmation running: User.validators_on(:password_confirmation)
Related
I am having trouble with devise validation. This is my model class (Note that I have enabled validatable):
class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable
end
And I try to validate the password confirmation using:
describe User do
before { #user = FactoryGirl.build(:user) }
subject { #user }
it { should validate_confirmation_of(:password) }
end
My factory:
FactoryGirl.define do
factory :user do
email { FFaker::Internet.email }
password "12345678"
password_confirmation "12345678"
end
end
But i got this error:
1) User should require password_confirmation to match password
Failure/Error: it { should validate_confirmation_of(:password) }
Expected errors to include "doesn't match Password" when password is set to "different value",
got no errors
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
Which means the devise validator is not triggered.
Now I add a custom validator:
validate :password_must_match
def password_must_match
errors.add(:password, "doesn't match confirmation") if password != password_confirmation
end
And got this error:
Failures:
1) User should require password_confirmation to match password
Failure/Error: it { should validate_confirmation_of(:password) }
Expected errors to include "doesn't match Password" when password is set to "different value",
got errors:
* "doesn't match Password" (attribute: password_confirmation, value: "some value")
* "doesn't match confirmation" (attribute: password, value: "different value")
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
As you can see, we now have 2 validation errors, "doesn't match Password" is from devise's validatable, and "doesn't match confirmation" is from my own custom validator.
I also tried using a custom test instead of it { should validate_confirmation_of(:password) }
describe "#confirmation_of_password" do
it "should fail when password does not match" do
expect { User.create!(email: "xxx#xxx.com", password: "123456", password_confirmation: "1234567") }.to raise_error
end
end
And it works. But I dont wanna reinvent the wheel from shoulda
In your first test You expect that it will validate confirmation of password but you pass in the same password so it never gets a chance to fail. SO you have 3 options. 1 use your first test but change the confirmation to not be the same(by passing in the params via factory girl). 2 you can expect to raise(if you do this you should specify which error will raise). Or you can also use Factory Girl's build method and expect the user to not be valid.
I am learning testing with RSpec. Something is not working with my tests.
My model:
class User < ActiveRecord::Base
has_secure_password
# Validation macros
validates_presence_of :name, :email
validates_uniqueness_of :email, case_sensitive: false
end
My factory:
FactoryGirl.define do
factory :user do
name "Joe Doe"
email "joe#example.com"
password_digest "super_secret_password"
end
end
And my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
it 'has a valid factory' do
expect(FactoryGirl.build(:user)).to be_valid
end
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:email) }
it { is_expected.to respond_to(:password) }
it { is_expected.to respond_to(:password_confirmation) }
it { expect(user).to validate_presence_of(:name) }
it { expect(user).to validate_presence_of(:email) }
it { expect(user).to validate_presence_of(:password) }
it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
end
I expected this test to pass. But I get this as a result:
Failures:
1) User should validate that :email is case-insensitively unique
Failure/Error: it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
User did not properly validate that :email is case-insensitively unique.
The record you provided could not be created, as it failed with the
following validation errors:
* name: ["can't be blank"]
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
Finished in 0.34066 seconds (files took 1.56 seconds to load) 9
examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:18 # User should validate that :email
is case-insensitively unique
What I am missing?
Update
I think that this is a bug: https://github.com/thoughtbot/shoulda-matchers/issues/830
It is because you are declaring it 2 times IMO! First building user then building same user inside expect().
Just use ur first user that you have built with factory-bot like so:
it 'has a valid factory' do
expect(user).to be_valid
end
P.S
It is better to use Faker gem instead of using harcoded instances like you did in factory.rb
Your Variable Is Currently Only Set Once for All Tests
When you write code like:
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
end
you aren't building a new user each time you run a new spec. Likewise, using #let is the wrong approach, because it memoizes the variable even between tests. Instead, you need a to use an RSpec before#each block. For example:
describe User do
before do
#user = FactoryGirl.build :user
end
# some specs
end
If you have tests which are persisting you user to the database, and if you have disabled rollback or database cleaning between tests, then your defined factory (as currently written) will certainly fail the uniqueness validation. In such cases, you may want to try:
User.delete_all in your test, or otherwise cleaning your database between tests.
Using FactoryGirl sequences or the Faker gem to ensure that user attributes are actually unique.
USE let
RSpec.describe User, type: :model do
let(:user) { FactoryGirl.build(:user) }
# other what you need
I am trying to Test the User Model Spec for User Creation
factories/users.rb
FactoryGirl.define do
factory :user do
first_name {Faker::Name.first_name}
last_name {Faker::Name.last_name}
email {Faker::Internet.email}
username {Faker::Internet.user_name}
password {Faker::Internet.password}
end
end
specs/models/user_spec.rb
require 'rails_helper'
describe User, :type => :model do
context "valid Factory" do
it "has a valid factory" do
expect(build(:user)).to be_valid
end
end
context "validations" do
before { create(:user) }
context "presence" do
it { should validate_presence_of :first_name }
it { should validate_presence_of :last_name }
it { should validate_presence_of :email }
it { should validate_presence_of :encrypted_password }
end
context "uniqueness" do
it { should validate_uniqueness_of :email }
it { should validate_uniqueness_of :username }
end
end
end
I am using Devise for the USer creation. But i am ending up with the following Test Failure
User
valid Factory
has a valid factory
validations
presence
should require first_name to be set
should require last_name to be set
should require email to be set
should require encrypted_password to be set (FAILED - 1)
uniqueness
should require case sensitive unique value for email
should require case sensitive unique value for username
Failures:
1) User validations presence should require encrypted_password to be set
Failure/Error: it { should validate_presence_of :encrypted_password }
Expected errors to include "can't be blank" when encrypted_password is set to nil,
got no errors
# ./spec/models/user_spec.rb:18:in `block (4 levels) in <top (required)>'
Finished in 0.98827 seconds (files took 6.61 seconds to load)
7 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:18 # User validations presence should require encrypted_password to be set
Am assuming that the encrypted_password will be auto generated by Devise on trying to create the user.
Devise does not actually validate the encrypted password since it is created dynamically after validation if the password is "dirty" (changed).
You don't actually need to test the encrypted password in that way since it is an implementation detail. You can test that Database authenticable is working properly by doing something like:
it 'is database authenticable' do
user = User.create(
email: 'test#example.com',
password: 'password123',
password_confirmation: 'password123'
)
expect(user.valid_password?('password123')).to be_truthy
end
But the actual value of the test is pretty low. Instead with Devise you you may want to focus on some end-to-end tests (feature specs) where you test the user path of signing up and then logging in.
I would suggest you only test those validations you added, you do not need to test validations added by devise since they are already been tested.
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: "tester#gmail.com", 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: "tester2#gmail.com", 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: "tester2#gmail.com",
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.
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) }
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: "tester2#gmail.com")
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...
I'm working through the Rails Tutorial and I've gotten stuck. I'm trying to use a password and password_confirmation.
I'm getting the error(s):
15) User when password confirmation is nil
Failure/Error: #user = User.new(name: "Example User", email: "user#example.com", password: "foobar", password_confirmation: "foobar")
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: password, password_confirmation
# ./spec/models/user_spec.rb:5:in `new'
# ./spec/models/user_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.21758 seconds
25 examples, 15 failures
Failed examples:
rspec ./spec/models/user_spec.rb:8 # User
rspec ./spec/models/user_spec.rb:9 # User
rspec ./spec/models/user_spec.rb:10 # User
rspec ./spec/models/user_spec.rb:11 # User
rspec ./spec/models/user_spec.rb:12 # User
rspec ./spec/models/user_spec.rb:14 # User
rspec ./spec/models/user_spec.rb:17 # User when name is not present
rspec ./spec/models/user_spec.rb:21 # User when name is too long
rspec ./spec/models/user_spec.rb:25 # User when email format is invalid should be invalid
rspec ./spec/models/user_spec.rb:33 # User when email format is invalid when email format is valid should be valid
rspec ./spec/models/user_spec.rb:47 # User when email address is already taken
rspec ./spec/models/user_spec.rb:55 # User when email address is already taken
rspec ./spec/models/user_spec.rb:59 # User when password is not present
rspec ./spec/models/user_spec.rb:63 # User when password doesn't match confirmation
rspec ./spec/models/user_spec.rb:67 # User when password confirmation is nil
All of the errors are for the same reason.
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :name
before_save { |user| user.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
#has_secure_password
has_many :event
end
user_spec.rb
require 'spec_helper'
describe User do
before do
#user = User.new(name: "Example User", email: "user#example.com", password: "foobar", password_confirmation: "foobar")
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
Any ideas would be appreciated.
Add password, :password_confirmation to attr_accessible in user.rb
attr_accessible :name, :email, :password, :password_confirmation
attr_accessible method takes list of attributes to be accessible. the other attribute will be protected see Mass Assignment for the reason.