I ran into a surprising Rails/Rspec/FactoryGirl (?) quirk today. I've since found a solution, but I'm interested in why the problem happened in the first place.
My example:
let(:old_user) { build(:user) }
let(:new_user) { create(:user) }
let(:post) { build(:post, user: old_user) }
it 'sets the new user' do
post.user_id = new_user.id
post.save
post.reload
post.user.should == new_user
end
This test fails. The user is not assigned properly, at the end of the test post.user is still old_user. (It also is a completely useless test.)
There are a couple of ways to get the test to pass. One is to create the old_user, so that post.user_id is set before trying to reassign it. Another is to replace post.user_id = new_user.id with post.user = User.find(new_user.id). But I'm curious...
What's happening behind the scenes with the original code when the test fails?
Edit: Adding in the factories as requested.
FactoryGirl.define do
factory :user do
# We actually use Faker, but for this purpose I gave strings
name 'Test User'
email 'example#example.com'
password 'changeme'
password_confirmation 'changeme'
end
end
# The Post class has `belongs_to :user`
FactoryGirl.define do
factory :post do
body 'I am a post'
end
end
Related
Been working on writing the correct test all day, but I can't figure it out.
A comment belongs to a user and an outlet
Each outlet has a user
Each out has many comments
Each user has many outlets
At the moment I'm getting this error:
Failure/Error: let(:outlet) { FactoryGirl.build(:outlet) }
ActiveRecord::RecordInvalid:
Validation failed: Username has already been taken, Email has already been taken
I really don't know what else to try. I've tried switching my factories and tests around a bunch, but only got different errors. Any help would be greatly appreciated
Factories:
FactoryGirl.define do
factory :comment do
body "This is a comment"
user
outlet
end
factory :invalid_comment, class: Comment do
body "This is a comment"
user nil
outlet nil
end
end
FactoryGirl.define do
factory :outlet do
category "vent"
title "MyString"
body "MyText"
urgency 1
user
end
factory :invalid_outlet, class: Outlet do
category "qualm"
title ""
body ""
urgency 3
user factory: :user
end
end
FactoryGirl.define do
factory :user do
sequence(:username) { |n| "test_user#{n}" }
sequence(:email) { |n| "test_user#{n}#email.com" }
password "password"
end
factory :invalid_user, class: User do
username ""
email ""
password ""
end
end
Test
describe 'create' do
context 'with valid attributes' do
let(:outlet) { FactoryGirl.create(:outlet) }
let(:comment_params) { FactoryGirl.attributes_for(:comment) }
let(:create) { post :create, params: { id: outlet , comment: comment_params } }
it "creates new comment" do
puts outlet
puts comment_params
expect { create }.to change { Comment.count }.by 1
end
end
end
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#outlet = Outlet.find(params[:id])
#comment = #outlet.comments.build(comment_params)
if #comment.save
redirect_to(#outlet)
end
end
private
def comment_params
params.require(:comment).permit(:body, :outlet_id, :user_id)
end
end
The error suggests that your generated username/emails are colliding with names in the database. That might happen if you have username created from one run still in your database on a later run.
If you are are not already using it, consider adding the DatabaseCleaner gem to your project. It provides various ways to ensure your database is reset between runs.
I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if a User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: post :create, { email: #users.first[:email] }
NoMethodError:
undefined method `name' for nil:NilClass
##myapp/gems/devise-3.2.4/app/controllers/devise_controller.rb:22:in 'resource_name'
# #myapp/gems/devise_invitable-1.3.6/lib/devise_invitable/controllers/helpers.rb:18:in 'authenticate_inviter!'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:67:in 'current_inviter'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:71:in 'has_invitations_left?'
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
Edit 1
Added my :user factory:
FactoryGirl.define do
factory :user do
association :family_tree
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password "password123"
password_confirmation "password123"
bio { Faker::Lorem.paragraph }
invitation_relation { Faker::Lorem.word }
# required if the Devise Confirmable module is used
confirmed_at Time.now
gender 1
end
end
It seems you're using Devise which require you to be logged in before going to the next step. On your error, Devise cannot get the same of your inviter because he's not logged.
Your test should be like this:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
#another_user = FactoryGirl.create(:user_for_login)
sign_in #another_user
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
Example for FactoryGirl model for Devise
factory :user_for_login, class: User do |u|
u.email 'admin#myawesomeapp.com'
u.password 'password'
u.password_confirmation 'password'
u.name "MyName"
end
Of course, you need to add as much data as your validators want.. Basically for Devise you need email, password and password_confirmation. In you case, it seems you also need name.
I'm trying to retrieve a record based on a user id. To make sure that my get controller's index works appropriately. Here is my controller snippet.
class SimulationsController < ApplicationController
def index
if current_user
#simulations = current_user.simulations
else
redirect_to new_user_session_path, notice: 'You are not logged in.'
end
end
Now I put some traces in my below controller spec. From what I'm gatherin from these traces there are four Simulations that exist for the test with the User_ID 1, however the User thats being created for the test, and the records along with it in the test are all User_ID 5. Can anyone give some guidance I'm pretty stumped beating my head on this. Additionally I'm getting a Response: OK. EDIT: Updated Spec with below answer.
require 'spec_helper'
describe SimulationsController, :type => :controller do
let :user do
FactoryGirl.create(:user)
end
before(:each) do
puts "~#{user.id}"
sign_in user
end
describe "GET #index" do
it "returns the correct number of simulations" do
simulation = FactoryGirl.build(:simulation, user: user)
simulation.save!
puts "#####{simulation.user_id}"
puts user.id
Simulation.all.each do |sim|
puts sim.user_id
end
get :index
puts "---\t\t#{response.body.size}"
# expect(response).to be_success
end
end
end
EDIT 2:
User Factory:
FactoryGirl.define do
factory :user do
email "user_#{User.last.nil? ? 1 : User.last.id + 1}#home.com"
password "password"
end
end
Simulation Factory:
FactoryGirl.define do
factory :simulation do |f|
f.id (Simulation.last.nil? ? 1 : Simulation.last.id + 1)
f.x_size 3
f.y_size 3
f.user_id 1
end
end
Final Edit: I was going about checking wrong, as outlined below, Body isn't what I was looking for I wanted to use assigns as I did below to check what I wanted too:
it "returns the correct number of simulations" do
simulation = FactoryGirl.build(:simulation, user: user)
simulation.save!
get :index
expect(assigns(:simulations).size).to eq 1
end
response.body is the rendered HTML, so its size would be length of that string. Depending on what your view looks like there might not be a straightforward correlation between its size and the number of simulations rendered.
In addition by default rspec doesn't render views at all in controller specs and so response.body will always be an empty string. You can change this by adding render_views to the example group.
Probably FactoryGirl overrides your user_id assignment, because you have association :user being set up there. Just change user_id to user and it should work:
simulation = FactoryGirl.build(:simulation, user: user)
UPD. And fix your factory:
FactoryGirl.define do
factory :simulation do |f|
# Never set ID manually
# f.id (Simulation.last.nil? ? 1 : Simulation.last.id + 1)
f.x_size 3
f.y_size 3
# f.user_id 1
# user 'association' method to set up associations
f.association :user
end
end
UPD2. To check if you controller assigned variable properly, use assigns:
expect(assigns(:simulations).length).to eq 4
You almost never should compare your response.body to anything – because, well, it's just raw body. To test your views you use special expectation methods, and to check instance #-variables assignments you use assigns.
I guess the problem is that I do not know how to use factory girl with Rspec correctly. Or testing in rails correctly for that matter. Still think it is a bit weird though..
I have a class, User, with the following factory:
FactoryGirl.define do
factory :user do
name "admin"
email "admin#admin.com"
adminstatus "1"
password "foobar"
password_confirmation "foobar"
end
factory :user_no_admin, class: User do
name "user"
email "user#user.com"
adminstatus "2"
password "foobar"
password_confirmation "foobar"
end
...
My test looks like this:
...
describe "signin as admin user" do
before { visit login_path }
describe "with valid information" do
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "User", with: user.name
fill_in "Password", with: user.password
click_button "Login"
end
it "should list users if user is admin" do
response.should have_selector('th', content: 'Name')
response.should have_selector('td', content: user_no_admin.name)
response.should have_selector('td', content: user.name)
end
end
end#signin as admin user
...
Basically I am trying to test that if you log in as an admin, you should see a list of all the users. I have a test for logging on as a non-admin later on in the file. I have a couple of users in the db already.
In the list of users 'admin' that logged in is displayed along with the users already in the db. 'user' is however not displayed unless I do something like this before:
fill_in "User", with: user_no_admin.name
fill_in "Password", with: user_no_admin.password
It is as if it won't exist unless I use it. However, if I use a puts it does print the information I am putting, even if I do not do the 'fill_in' above.
I have a similar example where a puts helps me.
describe "should have company name" do
let(:company) { FactoryGirl.create(:company) }
let(:category) { FactoryGirl.create(:category) }
let(:company_category) { FactoryGirl.create(:company_category, company_id: company.id, category_id: category.id) }
it "should contain companies name" do
puts company_category.category_id
get 'categories/' + company.categories[0].id.to_s
response.should have_selector('h4', :content => company.name)
end
end
Without the puts above I get a
Called id for nil
Do I have to initiate(?) an object created by Factory girl before I can use it in some way?
Any other code needed?
let(:whatever)
Is not creating the objects until the first time you call them. If you want it to be available before first use, use
let!(:whatever)
instead.
Or use a before block:
before(:each) do
#company = FactoryGirl.create(:company)
....
end
Which will create the objects before you need to use them.
Instead of:
factory :user do
name "admin"
email "admin#admin.com"
...
I will do:
factory :user do |f|
f.name "admin"
f.email "admin#admin.com"
...
Instead of:
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
I will do:
#user_no_admin = Factory(:user_no_admin)
#user = Factory(:user)
I had a similar issue with an existing test I broke, with a slightly different cause that was interesting.
In this case, the controller under test was originally calling save, but I changed it to call save!, and updated the test accordingly.
The revised test was:
Declaring the instance a let statement
Setting an expectation on the save! method (e.g. expect_any_instance_of(MyObject).to receive(:save!) )
Using the instance for the first time after the expectation.
Internally, it would appear that FactoryGirl was calling the save! method, and after changing the expectation from save to save!, no work was actually done (and the code under test couldn't find the instance from the DB)
that I needed to update and had a hard time getting to actually pass without a hack)
Try to use trait in the factory girl,there is an example as mentioned in the this link
I have a User model and Authentications model, which is a basic omniauth setup. Essentially, users can sign up through oauth without setting a password.
I have a Authentication.is_destroyable? method that returns true if the user has a password or has more than one authentication. Essentially, this prevents users deleting their one and only way of authentication.
def is_destroyable?
if user.encrypted_password.present? || user.authentications.count > 1
true
else
errors.add :base, 'not allowed'
false
end
end
When testing this in development it works as expected under all conditions. However, my unit tests are failing:
describe "Authentication#is_destroyable?" do
before(:each) do
# This creates a user with no password and a single authentication
#user = FactoryGirl.create(:user_with_oauth)
#auth = #user.authentications.first
end
# This spec passes :)
it "should return false when is users only authentication method" do
#auth.is_destroyable?.should be_false
end
# This FAILS - I have no idea why :(
it "should return true when user has multiple authentications" do
#user.authentications.create FactoryGirl.attributes_for(:authentication, :provider => 'twitter')
#auth.is_destroyable?.should be_true
end
# This FAILS - I have no idea why :(
it "should return true when user has a password" do
#user.update_attributes :password => 'password'
#auth.is_destroyable?.should be_true
end
end
I've spent the best part of 3 hours banging my head against the wall. I can't for the life of me understand why this works when I manually test the functionality (and Cucumber stories pass also testing the functionality), but in rspec the unit tests are failing. Is there something obvious that I'm missing?
Edit
As requested, here's some further detail.
Both failing specs fail with:
Failure/Error: #auth.is_destroyable?.should be_true
expected false to be true
The Factories:
FactoryGirl.define do
factory :user do
username { FactoryGirl.generate(:username) }
name 'Test User'
email { FactoryGirl.generate(:email) }
password 'password'
end
factory :user_with_oauth, :parent => :user do
password nil
authentications [ FactoryGirl.build(:authentication) ]
end
factory :authentication do
provider 'facebook'
uid SecureRandom.hex(16)
end
end
Also, maybe relevant, am using DatabaseCleaner with the truncation strategy.
I can answer my own question (after 2 more hours of hitting my head against the wall)...
My :user_with_oath Factory was to blame; I wasn't wrapping the authentications association in a block:
factory :user_with_oauth, :parent => :user do
password nil
authentications { [FactoryGirl.build(:authentication)] }
end