I have a user model that expects an email address to be unique however the models spec is failing:
spec/user_spec.rb
it "has a unique email" do
user1 = build(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end
app/model/user.rb
class User < ApplicationRecord
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
end
User has a unique email
Failure/Error: expect(user2).to_not be_valid
expected #<User id: nil, email: "example#email.com", created_at: nil, updated_at: nil> not to be valid
The uniqueness validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.
So, you just build two users in memory, neither of them is saved to database. Save the user1 to database, and then validate user2.
A possible change to your test may look like this:
it "has a unique email" do
user1 = create(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end
Related
I have a restriction on my Rails DB to force unique usernames, and I create a user at the start of each of my model tests. I'm trying to create a second user with the same username as the first and I expect this to not be valid, but it is returning as valid.
I've tried tweaking the code to use the new, save, and create methods when generating new users but with no success.
Registration Model:
class Registration < ApplicationRecord
#username_length = (3..20)
#password_requirements = /\A
(?=.{8,}) # Password must be at least 8 characters
(?=.*\d) # Password must contain at least one number
(?=.*[a-z]) # Password must contain at least one lowercase letter
(?=.*[A-Z]) # Password must contain at least one capital letter
# Password must have special character
(?=.*[['!', '#', '#', '$', '%', '^', '&']])
/x
validates :username, length: #username_length, uniqueness: true
validates :password, format: #password_requirements
validates :email, uniqueness: true
has_secure_password
has_secure_token :auth_token
def invalidate_token
self.update_columns(auth_token: nil)
end
def self.validate_login(username, password)
user = Registration.find_by(username: username)
if user && user.authenticate(password)
user
end
end
end
Registration Tests:
require 'rails_helper'
RSpec.describe Registration, type: :model do
before do
#user = Registration.new(
username: '1234',
password: 'abcdeF7#',
email: 'test#test.com',
name: 'One'
)
end
it 'should not be valid if the username is already taken' do
#user.save!(username: '1234')
expect(#user).not_to be_valid
end
end
I would expect this test to pass due to it being a duplicate username.
As fabio said, you dont have second Registration object to check uniquness.
You just checked your saved #user is valid or not which is always valid and saved in DB. To check your uniqueness validation you can do something like this -
RSpec.describe Registration, type: :model do
before do
#user = Registration.create(
username: '1234',
password: 'abcdeF7#',
email: 'test#test.com',
name: 'One'
)
#invalid_user = #user.dup
#invalid_user.email = "test1#test.com"
end
it 'should not be valid if the username is already taken' do
expect(#invalid_user.valid?).should be_falsey
end
end
#user.save! will raise an error even before reaching the expect as mentioned in comments by Fabio
Also, if it is important to you to test db level constraint you can do:
expect { #user.save validate: false }.to raise_error(ActiveRecord::RecordNotUnique)
I have 3 models that associate like so:
#user.rb
has_many :forums
has_many :posts
#forum.rb
belongs_to :user
has_many :posts
#post.rb
belongs_to :user
belongs_to :forum
I'm trying to create a single set of factories that all share the needed IDs needed to be associated with each other.
#User factory
FactoryGirl.define do
factory :user do
sequence :email do |n|
"testuser#{n}#postfactory.com"
end
password "password#1"
end
end
#Forum factory
FactoryGirl.define do
factory :forum do
user
name "Forum Name"
description "Forum Description with a minimum character count of 20"
end
end
#Post factory
FactoryGirl.define do
factory :post do
user
forum
title 'Post 1'
description 'This is a test description for Post 1'
end
end
When I run my spec test with:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum)
post = FactoryGirl.create(:post)
It outputs the following in the console:
#<User id: 1, email: "testuser1#userfactory.com", created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
#<Forum id: 1, name: "Forum Name", description: "Forum Description with a minimum character count o...", user_id: 2, created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
#<Post id: 1, title: "Post 1", description: "This is a test description for Post 1", user_id: 3, forum_id: 2, created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
As you can see, the user_id increments with each factory being created as well as forum_id. I would like these to all have the ID of 1 without having to do some manual work. What have I done incorrectly with my setup
Edit: I sort of see what I'm doing incorrectly. I only need to generate a post in my spec test and it will generate the factories needed (forum and user) to create the post. However, I do notice that I'm generating two users.
(byebug) User.count
2
(byebug) User.first
#<User id: 1, email: "testuser1#postfactory.com", created_at: "2016-10-27 20:30:33", updated_at: "2016-10-27 20:30:33">
(byebug) User.last
#<User id: 2, email: "testuser2#postfactory.com", created_at: "2016-10-27 20:30:33", updated_at: "2016-10-27 20:30:33">
Any idea why that is? I tried removing the sequence :email part and doing it standard. However, I get a validation error that the email has already been taken. For some reason, it's trying to run the user factory twice even though I call it only once in my spec test.
Every time you call FactoryGirl.create, there is a new created user, so after you run this code:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum)
post = FactoryGirl.create(:post)
actually you created 3 users, as you can see post has user_id: 3.
If you want to create forum and post with user you created, you can assign that user to forum and post when they are created:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum, user: user)
post = FactoryGirl.create(:post, user: user, forum: forum)
With this code, there is only one created user.
I have a user model, who have a many-to-many relationship whit itself: user A add user B as a friend, and automatically, user B becomes friend of user A too.
Performing the following steps in the rails console:
1) Create two users and save them:
2.3.1 :002 > u1 = User.new(name: "u1", email: "u1#mail.com")
=> #<User _id: 5788eae90640fd10cc85f291, created_at: nil, updated_at: nil, friend_ids: nil, name: "u1", email: "u1#mail.com">
2.3.1 :003 > u1.save
=> true
2.3.1 :004 > u2 = User.new(name: "u2", email: "u2#mail.com")
=> #<User _id: 5788eaf80640fd10cc85f292, created_at: nil, updated_at: nil, friend_ids: nil, name: "u2", email: "u2#mail.com">
2.3.1 :005 > u2.save
=> true
2) Add user u2 as friend of u1:
2.3.1 :006 > u1.add_friend u2
=> [#<User _id: 5788eaf80640fd10cc85f292, created_at: 2016-07-15 13:54:04 UTC, updated_at: 2016-07-15 13:55:19 UTC, friend_ids: [BSON::ObjectId('5788eae90640fd10cc85f291')], name: "u2", email: "u2#mail.com">]
3) Check their friendship:
2.3.1 :007 > u1.friend? u2
=> true
2.3.1 :008 > u2.friend? u1
=> true
As we can see, the "mutual friendship" works. But in my tests that doesn't happen. Here are my tests:
require 'rails_helper'
RSpec.describe User, type: :model do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
context "when add a friend" do
it "should put him in friend's list" do
user.add_friend(other_user)
expect(user.friend? other_user).to be_truthy
end
it "should create a friendship" do
expect(other_user.friend? user).to be_truthy
end
end
end
Here are the tests result:
Failed examples:
rspec ./spec/models/user_spec.rb:33 # User when add a friend should create a friendship
The only reason that I can see to the second test is failing is because my let is not memoizing the association to use in other tests. What am I doing wrong?
Here is my User model, for reference:
class User
include Mongoid::Document
include Mongoid::Timestamps
has_many :posts
has_and_belongs_to_many :friends, class_name: "User",
inverse_of: :friends, dependent: :nullify
field :name, type: String
field :email, type: String
validates :name, presence: true
validates :email, presence: true
index({ email: 1 })
def friend?(user)
friends.include?(user)
end
def add_friend(user)
friends << user
end
def remove_friend(user)
friends.delete(user)
end
end
You need to move the creation of the relationship into a before block:
context "when add a friend" do
before do
user.add_friend(other_user)
end
it "should put him in friend's list" do
expect(user.friend? other_user).to be_truthy
end
it "should create a friendship" do
expect(other_user.friend? user).to be_truthy
end
end
In your code, you are only running it within the first it block, to the second one starts from scratch and it's not run.
With the before block, it is run once before each of the it blocks, so the spec should pass then.
I want to create an object and then delete it during an rspec test... is this possible?
This code:
describe User do
it "should be invalid if email is not unique" do
user = FactoryGirl.create(:user, id: 1, email: "g#g.com").should be_valid
FactoryGirl.build(:user, email: "g#g.com").should_not be_valid
User.destroy(user.id)
FactoryGirl.build(:user, email: "g#g.com").should be_valid
end
end
returns the error:
Failure/Error: User.destroy(user.id)
NoMethodError:
undefined method `id' for true:TrueClass
The issue isn't that you can't delete the user within your rspec test - it's that your user object isn't getting the assignment you intended:
FactoryGirl.create(:user, id: 1, email: "g#g.com").should be_valid
evaluates to true, and is assigned to user. Then, when you try to call user.id, it looks for the id property of true and gets stuck.
You might split out that particular line to:
FactoryGirl.build(:user, id: 1, email: "g#g.com").should be_valid
user = FactoryGirl.create(:user, id: 1, email: "g#g.com")
or not worry about local user variable and just delete based on the email address, i.e.
User.destroy((User.find_by email: "g#g.com").id)
I have the following factory:
FactoryGirl.define do
factory :user do
name 'Name'
password 'password'
email 'email#example.com'
end
end
I have the following code in before block (I am creating all possible variations of email-some_boolean_flag pairs where email can take '' and default value and some_boolean_flag can be false/nil or true):
FactoryGirl.create(:user, email: '', some_boolean_flag: false)
FactoryGirl.create(:user, email: '', some_boolean_flag: true)
FactoryGirl.create(:user, some_boolean_flag: nil)
FactoryGirl.create(:user, some_boolean_flag: true)
How can I DRY it? Is there any way in FactoryGirl to create a list of objects but with specific attributes being different and without repeating same line over and over? Thanks!
Factory:
FactoryGirl.define do
factory :user do
name 'Name'
password 'password'
email 'email#example.com'
factory :boolean_user
some_boolean_flag true
end
end
end
Test
['', 'email#example.com'].each do |email|
FactoryGirl.create(:user, email: email)
FactoryGirl.create(:boolean_user, email: email)
end
A note here, I am purposefully going with restating the 'email#example.com' because I like my factories to be what's needed to pass validations. I don't like to depend on the contents of a factory for my test to pass. I will always specifically call the data I need.