How to get proper email uniqueness in RSpec - ruby-on-rails

Rspec throws errors:
1) Client uniqueness validates uniqueness of email
Failure/Error: expect(subject).to validate_uniqueness_of :email
Client did not properly validate that :email is case-sensitively unique.
After taking the given Client, whose :email is
‹"jaleel_wehner#okonwiegand.name"›, and saving it as the existing
record, then making a new Client and setting its :email to a different
value, ‹"JALEEL_WEHNER#OKONWIEGAND.NAME"›, the matcher expected the
new Client to be valid, but it was invalid instead, producing these
validation errors:
* pesel: ["This pesel is already in database"]
* email: ["This email is already in database"]
In model I have implemented uniqueness and case-sensitive: false for email.
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: VALID_EMAIL_REGEX }
I also have implemented method, that all email downcase before validation.
def downcase_email
self.email = email.downcase if email.present?
end
before_validation :downcase_email
Why matcher expected that new Client will be valid? It should be invalid.
subject { FactoryGirl.build(:client) }
it 'validates uniqueness of email' do
expect(subject).to validate_uniqueness_of :email
end
Client has a valid factory. I tried find good solution, but I haven't found anything that would solve my problem.
FactoryGirl.define do
factory :client do
pesel { Faker::Number.number(11) }
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
date_of_birth { Faker::Time.between('1970-01-01', '2000-12-31') }
email { Faker::Internet.email }
password { Faker::Internet.password }
type 'Client'
end
end

FactoryGirl has the following example for your issue within it's documentation
sequence :email do |n|
"person#{n}#example.com"
end
factory :invite do
invitee { generate(:email) }
end
Edit after your updates:
The issue is the matcher validate_uniqueness_of. You have to adjust case_sensitive for the matcher too. So it should be validate_uniqueness_of(:email).case_insensitive

Check if your factory has curved brackets wrapping the email attribute like such:
FactoryGirl.define do
factory :client do
email { Faker::Internet.email }
end
end

Related

rails 5 validators not protecting against a missing attribute during Model create?

cannot seem to get my validators to work to ensure all attributes are present to allow a User to be created. Basic User with 2 attributes
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
tests to check that name and email are present when created. these #pass
RSpec.describe User, type: :model do
context 'validations' do
subject { FactoryGirl.build(:user) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_presence_of(:name) }
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1', :noemail => 'c#c.co')}.to raise_error(ActiveModel::UnknownAttributeError)
end
end
end
but if i try and create model with a missing attribute no error is raised
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end
result
1) User validations fails to create user unless both are present
Failure/Error: expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
expected ActiveModel::MissingAttributeError but nothing was raised
# ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
fyi, FactoryGirl
FactoryGirl.define do
factory :user do
name "MyString"
email "MyString"
end
end
i have tried clever stuff like
class User < ApplicationRecord
# before_create :run_it
after_initialize :all_present?
validates :name, presence: true
validates :email, presence: true
private
def all_present?
if (#email.nil? || #name.nil?)
raise ActiveModel::MissingAttributeError.new()
end
end
end
but cannot seem to raise these manually...?
what am i doing wrong?
tx all
Ben
The problem is that there are 2 methods, create and create!. The first, create
The resulting object is returned whether the object was saved successfully to the database or not
Whereas with create!:
Raises a RecordInvalid error if validations fail, unlike Base#create
So, create fails silently and doesn't raise any exceptions, but you can still inspect the instance and see that it's a new record and has errors and such, and create! fails noisily, by raising the error you are expecting it to raise. In short, your test should be:
it "fails to create user unless both are present" do
expect { User.create!(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end

Rspec Spec - User Model validation on unique email account - spec failing

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

Testing conditional validation with controller inputs in Rails

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

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: "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...

rails factory girl getting "Email has already been taken"

This is my factory girl code, and every time I try to generate a review, it's telling me that "Email has already been taken", i've reset my databases, set the transition in spec_helper to true, but still haven't solved the problem. I'm new to this, am I using the association wrong? Thanks!
Factory.define :user do |user|
user.name "Testing User"
user.email "test#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
Factory.define :course do |course|
course.title "course"
course.link "www.umn.edu"
course.sections 21
course.description "test course description"
course.association :user
end
Factory.define :review do |review|
review.title "Test Review"
review.content "Test review content"
review.association :user
review.association :course
end
I know this is a pretty old question, but the accepted answer is out of date, so I figured I should post the new way of doing this.
FactoryGirl.define do
sequence :email do |n|
"email#{n}#factory.com"
end
factory :user do
email
password "foobar"
password_confirmation "foobar"
end
end
Source: Documentation
It's quite a bit simpler, which is nice.
You need to use a sequence to prevent the creation of user objects with the same email, since you must have a validation for the uniqueness of emails in your User model.
Factory.sequence :email do |n|
“test#{n}#example.com”
end
Factory.define :user do |user|
user.name "Testing User"
user.email { Factory.next(:email) }
user.password "foobar"
user.password_confirmation "foobar"
end
You can read more in the Factory Girl documentation.
In addition to the above answers you could add gem 'faker' to your Gemfile and it will provide unique emails.
FactoryGirl.define do
factory :admin do
association :band
email { Faker::Internet.email }
password "asdfasdf"
password_confirmation "asdfasdf"
end
end
sequence gives really unique email and Faker gives random password.
FactoryGirl.define do
sequence :email do |n|
"user#{n}#test.com"
end
factory :user do
email
password { Faker::Internet.password(min_length: 8, max_length:20) }
password_confirmation { "#{password}" }
end
end
For some reason the password_confirmation field wasn't working for me. What worked was this:
FactoryBot.define do
sequence :email do |n|
"user#{n}#test.com"
end
factory :user do
email
password { Faker::Internet.password(min_length: 8, max_length:20) }
confirmed_at { Time.current } # <---- This worked for me
end
end
Note that if you're not using Faker, you can have something as simple as `password { "password" } instead of that line.

Resources