I'm writing a cost for a create method that will add a comment to a post.
The comment belongs to a user and a post. And a post belongs to a user.
When I run my test I get a validation error saying that the username and email have already been taken. I've tried using build as well as build_stubbed in both my factories and in the test, but neither of them worked. I think it has to do with the fact that I'm using create, but I'm not entirely sure.
Any advice would be much appreciated
Here are my factories:
users.rb
FactoryGirl.define do
factory :user do
username "test_user"
email "test_user#email.com"
password "password"
end
factory :user_2, class: User do
username "test_user_2"
email "test_user_2#email.com"
password "password"
end
factory :invalid_user, class: User do
username ""
email ""
password ""
end
end
outlets.rb
FactoryGirl.define do
factory :outlet do
category "vent"
title "MyString"
body "MyText"
urgency 1
user factory: :user
end
factory :outlet_2, class: Outlet do
category "rant"
title "MyString_2"
body "MyText_2"
urgency 2
user factory: :user_2
end
factory :invalid_outlet, class: Outlet do
category "qualm"
title ""
body ""
urgency 3
user factory: :user
end
end
comments.rb
FactoryGirl.define do
factory :comment do
body "This is a comment"
user factory: :user
outlet factory: :outlet_2
end
factory :invalid_comment, class: Comment do
body "This is a comment"
user nil
outlet nil
end
end
Here is my test:
describe 'create' do
context 'with valid attributes' do
let(:outlet) { FactoryGirl.create(:outlet) }
let(:valid_comment_params) { FactoryGirl.attributes_for(:comment) }
it "creates a new comment" do
expect { post :create, params: { id: outlet, :comment => valid_comment_params } }.to change(Comment, :count).by(1)
end
end
end
Here are my models:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :outlet
validates :body, :user, :outlet, presence: true
validates :body, length: { in: 1..1000 }
end
class Outlet < ApplicationRecord
belongs_to :user
has_many :comments
validates :category, :title, :body, :urgency, :user, presence: true
validates :title, length: { in: 1..60 }
validates :body, length: { in: 1..1000 }
validates :urgency, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 10 }
validates :category, inclusion: { in: ['vent', 'rant', 'qualm'] }
end
class User < ApplicationRecord
has_many :outlets
has_many :comments
validates :username, :email, :encrypted_password, presence: true
validates :username, :email, uniqueness: true
validates :password, length: { in: 5..30 }
# Include default devise modules. Others available are:
# :lockable, :timeoutable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
end
So the problem here is you keep trying to create a user with the same email and username that you just created another user with. In order to avoid this in your factories, you should strive to make the values dynamic. Since the main issues currently are the uniqueness validations, lets start with those.
factory :user do
sequence(:username) { |n| "test_user#{n}" }
sequence(:email) { |n| "test_user#{n}#email.com" }
password "password"
end
that way, you can use the same factory to create 2 distinct users
user = FactoryGirl.create :user
user_2 = FactoryGirl.create :user
Related
I am using the Rolify gem in a Rails 4.2 application.
I am using seed data in the factory which works. However, from what I have read, that's not the correct way to do it.
I'm having trouble getting my head around how to do so correctly without having to use complex factory calls in my spec files.
Here is my Employee factory:
require 'faker'
#Seed the database before running the tests only if in test mode
#Fixes problem with rake routes running this command and deleting out the database
Rails.application.load_seed if Rails.env.test?
FactoryGirl.define do
factory :employee do
first_name { Faker::Name.first_name}
last_name { Faker::Name.last_name}
sequence(:email) { |n| "peterjohnson#{n}#example.com" }
mobile 66816927867
bio "MyText"
address { Faker::Address.street_address}
province_state { Faker::Address.state}
country { Faker::Address.country}
postal_code { Faker::Address.postcode}
status :active
bachelor_degree "B.Sc"
password Faker::Internet.password(8)
sequence(:paypal_email) { |n| "paypal_peterJohnson#{n}#example.com" }
sequence(:skype_id) {|n| "peterjohnson_skype#{n}" }
os :mac
role_ids [Role.first.id]
trait :proofreader do
after(:create) {|employee| employee.add_role(:proofreader)}
end
trait :admin do
after(:create) {|employee| employee.add_role(:admin)}
end
trait :super_admin do
after(:create) {|employee| employee.add_role(:super_admin)}
end
end
end
I need to have the roles created before the Employee so that I can save their IDs in the Employee model.
In my application Roles are added via a form when an employee is registered. So I can't do an after create to add the role for some of the tests.
If I create the roles within the Employee factory as follows:
factory :employee do
["proofreader", "admin", "super_admin"].each do |role|
FactoryGirl.create(:role, name: role)
end
first_name { Faker::Name.first_name}
...
I get an error telling me that it can't find the factory named role. It exists as follows:
FactoryGirl.define do
factory :role do
name :proofreader
end
end
Here is my Employee model which validates that an Employee must have a role:
class Employee < ActiveRecord::Base
# returns the full name of the employee. This code is found in a concern called name.rb
include Name
rolify
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
enum status: [:active, :vacation, :unemployed]
enum os: [:mac, :windows]
validates :first_name,
presence: true,
length: {minimum: 2}
validates :last_name,
presence: true,
length: {minimum: 2}
validates :email, email: true, presence: true, uniqueness: true
validates :paypal_email, email: true, presence: true, uniqueness: true
validates :skype_id, presence: true, uniqueness: true
validates :mobile, presence: true, numericality: true, length: {minimum: 10}
validates :address, :province_state, :country, :postal_code, :bachelor_degree, :os,:status, :role_ids, presence: true
end
So how can I create the Employee with a role without seeding the database?
You're attempting to FactoryGirl.create(:role) when factory_girl reads the :employee factory definition. The :employee factory is in employee.rb and the :role factory is in role.rb (or something like that), and factory_girl probably reads factory definitions in alphabetical order by file name, so when :employee is defined :role doesn't exist yet.
You need to create all roles when the test runs, after all factories have been defined.
In the :employee factory, change how you initialize role_ids:
role_ids { [(Role.first || FactoryGirl.create(:role, name: :default)).id] }
(Actually you should just be able to set roles instead of role_ids so you don't have to call .id on the default role.)
Then add the additional role in a callback in a trait:
trait :proofreader do
after(:create) do |employee|
FactoryGirl.create :role, name: :proofreader
employee.add_role(:proofreader)
end
end
However, assuming your roles don't change except when your application code also changes, I'd use seeds. It would be a lot simpler, and I don't know why it would be incorrect. I've done it with seeds on several projects and it works well.
I have three models user (author), which is incorporating devise logic:
app/models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :questions
has_many :answers
end
question:
app/models/question.rb
# Model for Question
class Question < ActiveRecord::Base
has_many :answers, dependent: :destroy
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true, length: { minimum: 10 }
validates :author, presence: true
end
and answer:
app/models/answer.rb
# Model for Answer
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
validates :body, presence: true, length: { minimum: 10 }
validates :question_id, presence: true
validates :author, presence: true
end
and their factories:
spec/factories/users.rb
FactoryGirl.define do
sequence :email do |n|
"email-#{n}#example.com"
end
sequence :password do |n|
"testpassword#{n}"
end
factory :user, aliases: [:author] do
email
# tried sequence generator and fixed password - both have no impact on result
# password '1234567890'
# password_confirmation '1234567890'
password
end
end
spec/factories/answers.rb
FactoryGirl.define do
factory :answer do
body 'Answer Body'
author
question
end
factory :nil_answer, class: 'Answer' do
question
body nil
end
end
spec/factories/questions.rb
FactoryGirl.define do
factory :question do
title 'Question Title'
body 'Question Body'
author
factory :question_with_answers do
after(:create) do |question|
# changing create_list to create has no impact on result
# create_list(:answer, 2, question: question)
create(:answer, question: question)
end
end
end
end
test code:
spec/features/delete_answer_spec.rb
require 'rails_helper'
feature 'Delete answer', %q{
By some reason
As an authenticated user
I want to delete answer
} do
given(:question) { create(:question_with_answers) }
given(:user) { create(:user) }
given(:ans) { create(:answer) }
scenario 'Answer author password should not be nil' do
expect(question.answers.first.author.password).to_not be_nil
# question.author.password and ans.author.password return not nil
# I need password to do:
# visit new_user_session_path
# fill_in 'Email', with: user.email
# fill_in 'Password', with: user.password
# click_on 'Log in'
end
end
Can anyone explain why the following given statement:
given(:question) { create(:question_with_answers) }
creates question object that:
question.author.password #=> '1234567890'
but:
question.answers.first.author.password #=> nil
why method "create" instantiates author of question properly (field password is set), but "create_list" inside "after" callback creates author in answer with nil fields?
rails 4.2.5, ruby 2.3.0, devise 3.5.6, warden 1.2.6, factory_girls_rails 4.6.0 (4.5.0)
Devise (and most authentication libraries) encrypt the password and don't allow you to access passwords from models retrieved from the database. The password may be temporarily available through an in-memory reader method, but won't be available if you retrieve the record from the database.
If you do:
user = User.new(password: "example")
p user.password
I'm guessing you'll see "example".
But if you do:
user = User.first
p user.password
I bet you'll see nil (assuming you have user records in your database).
When you query an association proxy like question.answers.first.author, it's going to the database again to find the answer and author. That means you're using a different instance, which no longer has the password available.
I have two models Users and Accounts which has the relationship
Users has_many Accounts via `created_by_id`
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
accounts.rb
FactoryGirl.define do
factory :account do
name {Faker::Company.name}
association :user
end
end
Models
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, :confirmable, :invitable
has_many :accounts
# validations
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true,
uniqueness: {case_sensitive: false},
:email => true
validates :username, uniqueness: {case_sensitive: false, allow_blank: true}
before_validation :downcase_attributes
def name
"#{first_name} #{last_name}"
end
private
def downcase_attributes
self.email = email.try(:downcase)
self.username = username.try(:downcase)
end
end
Accounts Model
class Account < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
belongs_to :users, foreign_key: "created_by_id"
# Methods
def should_generate_new_friendly_id?
name_changed?
end
end
When i try to Testing the Validity of the Factories by the following
require 'rails_helper'
describe Account, type: :model do
context "valid Factory" do
it "has a valid factory" do
expect(build(:account)).to be_valid
end
end
context "validations" do
before { create(:account) }
context "presence" do
it { should validate_presence_of :name }
end
end
end
Error
Account
valid Factory
has a valid factory (FAILED - 1)
validations
presence
example at ./spec/models/account_spec.rb:15 (FAILED - 2)
Failures:
1) Account valid Factory has a valid factory
Failure/Error: expect(build(:account)).to be_valid
NoMethodError:
undefined method `user=' for #<Account:0x007f858a4783a0>
# ./spec/models/account_spec.rb:7:in `block (3 levels) in <top (required)>'
2) Account validations presence
Failure/Error: before { create(:account) }
NoMethodError:
undefined method `user=' for #<Account:0x007f8595154088>
# ./spec/models/account_spec.rb:12:in `block (3 levels) in <top (required)>'
Finished in 0.87731 seconds (files took 5.61 seconds to load)
2 examples, 2 failures
Failed examples:
rspec ./spec/models/account_spec.rb:6 # Account valid Factory has a valid factory
rspec ./spec/models/account_spec.rb:15 # Account validations presence
This line:
belongs_to :users, foreign_key: "created_by_id"
should be changed to:
belongs_to :user, foreign_key: :created_by_id # notice singular :user
From docs:
A belongs_to association sets up a
one-to-one connection with another model, such that each instance of
the declaring model "belongs to" one instance of the other model. ...
I have three models User, Post, Vote
I need to doesn't allow user to vote for his own post.
How I can make this in my models and test this in Rspec?
Post Model:
class Post < ActiveRecord::Base
attr_accessible :title, :main_text, :video, :photo, :tag
validates :title, presence: true, length: {minimum: 1, maximum: 200}
validates :main_text, presence: true, length: {minimum: 1}
belongs_to :user
has_many :votes
end
User Model:
class User < ActiveRecord::Base
attr_accessible :name, :email, :bio
has_many :posts
has_many :votes
validates :name, presence: true, length: {minimum: 1, maximum: 120}
validates :email, presence: true, length: {minimum: 5, maximum: 250}, uniqueness: true,
format: {:with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i}
end
Vote model:
class Vote < ActiveRecord::Base
attr_accessible :user_id, :post_id, :units
belongs_to :user
belongs_to :post
validates :post_id, uniqueness: {scope: :user_id} #does not allow user to vote for the same post twice
end
My spec test for Vote:
require 'spec_helper'
describe Vote do
it "does not allow user to vote for the same post twice" do
user = User.create(name: "Nik", email: "nik#google.com" )
post = Post.create(title: "New Post", main_text: "Many, many, many...")
vote1 = Vote.create(user_id: user.id, post_id: post.id)
vote1.errors.should be_empty
vote2 = Vote.create(user_id: user.id, post_id: post.id)
vote2.errors.should_not be_empty
end
it "does not allow user to vote for his own post" do
user = User.create(name:"Nik", email:"a#a.ru")
post = Post.create(user_id: user.id, title: "New Post", main_text: "Many, many, many...")
vote1 = Vote.create(user_id: user.id, post_id: post.id)
vote1.errors.should_not be_empty
end
end
I've not tested the following code so it could not work or even kill your cats but try with a custom validation with add an error if the vote's user is the same of the post's user.
Note I return if the user or the post are nil for obvius reasons.
# In your vote model
validate :users_cant_vote_their_posts
def users_cant_vote_their_posts
return if user.nil? or post.nil?
if user_id == post.user_id
errors[:base] = "A user can't vote their posts"
end
end
EDIT: Here a possible test, here I'm using FactoryGirl to generate votes. Again this code is not tested (sorry for the pun)
describe Vote do
subject { vote }
let!(:vote) { FactoryGirl.build(:vote) }
it 'from factory should be valid' do
should be_valid
end
context 'when user try to double vote' do
before do
# Create another vote from user to post
FactoryGirl.create(:vote, :user => vote.user, :post => vote.post)
end
it { should_not be_valid }
end
context 'when user try to vote his posts' do
before do
# Set the user whom voted to the post's owner
vote.user = vote.post.user
end
it { should_not be_valid }
end
end
I don't know ruby, but you should check whether the user signed in matches the user that made the post. And if it does, deny the request to vote.
Sorry if this is not helpful :)
FactoryGirl.define do
factory :agency do
name "Example Inc"
available_items "20"
recruiter # recruiter.id
end
factory :recruiter do
email 'example#example.com'
password 'please'
password_confirmation 'please'
# required if the Devise Confirmable module is used
# confirmed_at Time.now
end
end
agency.rb
class Agency < ActiveRecord::Base
belongs_to :recruiter
validates :name, :presence => true
end
recruiter.rb
class Recruiter < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessible :agency_attributes, :first_name
has_one :agency, :dependent => :destroy
accepts_nested_attributes_for :agency
validates :email, :presence => true
end
authentication_steps.rb
def create_user
#recruiter = FactoryGirl.create(:recruiter)
end
How can I replicate this Recruiter & Agency association using factory_girl?
I think you should remove recruiter from agency factory and add agency to requiter factory
FactoryGirl.define do
factory :agency do
name "Example Inc"
available_items "20"
factory :agency_without_recuiter do
recuiter_id = 1
end
factory :agency_with_recuiter do
recuiter
end
end
factory :recuiter do
email 'example#example.com'
password 'please'
password_confirmation 'please'
factory :recuiter_with_agency
agency
end
end
end
This should work from both sides
create(:agency).recuiter => nil
create(:agency_with_recuiter).recuiter => recuiter
create(:recuiter).agency => nil
create(:recuiter_with_agency).agency => agency
Hope it will be usefull. Good luck!
I think you have to replicate it in your test cases, not in FG itself.
before (:each) do
#recruiter = FactoryGirl.create(:recruiter)
#agency = FactoryGirl.create(:agency)
#agency.recruiter = #recruiter
end
Something like this.