I've got user devise model with validations
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :first_name, presence: true, length: { maximum: 100 }
validates :last_name, presence: true, length: { maximum: 100 }
validates :email, presence: true, uniqueness: true, format: { with: /\A.+#.+\..+\z/ }
end
and RSpec file to test email validation
describe 'email field' do
subject { User.new(first_name: 'jan', last_name: 'kowalski', email: 'jan#foo.com').valid? }
context 'when email has wrong format' do
let(:email) { 'jan#foo' }
it 'complains for invalid format' do
is_expected.to eq false
end
let(:email) { 'jan' }
it 'complains for invalid format' do
is_expected.to eq false
end
end
context 'when email has correct format' do
it 'accepts valid format' do
is_expected.to eq true
end
end
end
I want to test the validations for correct email address format which is in user model. Every test passed well except the last where I have an error expected: true got: false. Did I miss something in a spec file? or maybe I have wrong declaration in user model? Any helps are welcomed.
you have some mistakes in your spec file, the two let of email are not execution in anyway.
if you want that behavior you need to replace the email option in your subject by a variable, and your second it you need to wrap it in a context and put inside you let(:email), this is the way that rspec is going to replace the value of your subject in each it.
Here an example but using a password variable, also your two first test are passing, because your are missing the password that way your expec is false, but they are not test the test your are describing.
describe 'password field' do
subject { Usuario.new(nombre: 'jan', username: 'kowalski', password: password).valid? }
context 'when password has wrong format' do
let(:password) { nil }
it 'complains for invalid format' do
is_expected.to eq false
end
end
context 'when password size is incorrect' do
let(:password) { 'jan' }
it 'complains for invalid format' do
is_expected.to eq false
end
end
context 'when password has correct format' do
let(:password) { '1qaz2wsx' }
it 'accepts valid format' do
is_expected.to eq true
end
end
end
Related
Error message I am getting for my failing user spec is
User did not properly validate that :email is case-sensitively unique.
User Spec as follows:
it { expect(subject).to respond_to :emails }
it { expect(subject).to validate_uniqueness_of(:email).case_insensitive }
it 'has the correct format' do
expect(subject).to allow_value(Faker::Internet.email).for(:email)
expect(subject).to_not allow_value("'#{Faker::Internet.email}'").for(:email)
expect(subject).to allow_value("'some-thing.odd#example.com").for(:email)
end
it "requires a unique email" do
expect(subject).to validate_uniqueness_of(:email)
end
end`
Im not sure what I actually need to return for this to check case -sensitively uniqie, these are the first specs I am working on so also new to rspec but currently working through this book to understand rspec and testing better http://ruby-doc.com/docs/ProgrammingRuby/
User Model:
validates :email, presence: true, format: { with: ValidateEmail::REGEXP }
validates :alias_email, format: { with: ValidateEmail::REGEXP, allow_blank: true }
validates_uniqueness_of :email, case_sensitive: false
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 have custom validator for password that takes a updating_password field from the controller
attr_accessor :updating_password
validates :password, presence: true, if: :should_validate_password?
validates :password, length: { minimum: 6 }, if: :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
I want to stub out the updating_password field in my User model RSpec test, something like
before(:each) do
#user_valid = FactoryGirl.create(:user)
end
it "validates for password when updating_password is true" do
old_password = #user_valid.password
subject { #user_valid }
allow(subject).to receive(:updating_password).and_return(true)
#user_valid.update(password: "short", password_confirmation: "short")
expect(#user_valid.password).to eql(old_password)
end
The password should not be updated in this case because it is too short but the test is failing. Any help would be appreciated
I'd recommend not stubbing out your model validations. Instead you might test what you're trying to do like this
# spec/models/user_spec.rb
describe User do
describe 'validations' do
context 'while updating password' do
let(:user){ FactoryGirl.create(:user, updating_password: true) }
it 'requires password to be at least 6 characters long' do
expect {user.update!(password: 'short')}.to raise_error(ActiveRecord:RecordInvalid)
end
it 'requires password to be present' do
expect {user.update!(password: nil))}.to raise_error(ActiveRecord:RecordInvalid)
end
end
end
end
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.