Why are these FactoryGirl factory instances not being created to pass this index test? - capybara

I am trying to test presence of all model objects in an index view, index renders just fine with manual in browser use, so the problem must be with with my FactoryGirl factory and or the way I'm passing arguments into the FactoryGirl.create method, though research fails to reveal to me what exactly I'm doing wrong, any ideas? Thanks
quotes.rb
FactoryGirl.define do
factory :quote do
prev_cover true
sequence(:co_name) { |n| "Acme Co #{n}" }
co_number 9999
postcode "al1 1aa"
industry :financial_services
lives_overseas true
scheme_start_date "2018-01-01"
payment_frequency :monthly
commission_level 12
gla 1
factory :quote2 do
end
factory :quote3 do
end
end
end
quotes_page_spec.rb
require 'rails_helper'
feature 'index page' do
let(:user) { FactoryGirl.create(:user) }
let(:quote) { FactoryGirl.create(:quote, user: user, co_name: "Co1") }
let(:quote2) { FactoryGirl.create(:quote, user: user, co_name: "Co2") }
let(:quote3) { FactoryGirl.create(:quote, user: user, co_name: "Co3") }
before do
login_as(user, :scope => :user)
end
scenario 'when user view all their quotes' do
visit("/quotes")
expect(page).to have_content("Co1")
expect(page).to have_content("Co2")
expect(page).to have_content("Co3")
end
This is the failure message i get;
1) index page when user view all their quotes
Failure/Error: expect(page).to have_content("Co1")
expected to find text "Co1" in "Toggle navigation QuoteEngine My Quotes My Account Sign out"
# ./spec/features/quotes_page_spec.rb:15:in `block (2 levels) in <top (required)>'

The issue here is that you’re using let. let is lazily evaluated, so the object is not actually created until the first time it is referenced in the test. Since you never reference the quote objects in your test they are never created. To have the objects always created use let! rather than let

Error was that I was not actually creating the multiple quotes by declaring them as lets. Moved the quotes that I want to be created into a before block and all is well, as below:
let(:user) { FactoryGirl.create(:user) }
before do
login_as(user, :scope => :user)
FactoryGirl.create(:quote, user: user, co_name: "Co1")
FactoryGirl.create(:quote, user: user, co_name: "Co2")
FactoryGirl.create(:quote, user: user, co_name: "Co3")
end

Related

Where to create parent model instances in feature test (Capybara)?

I am testing the creation of a product with Capybara, i.e., I am filling a form with automated test. This product belongs to certain models, for example, to a home.
I have two factory files to create this two models, the product and the house. In the form, user should select the home from a select (Drop down). I manage to do it, but the solution feels not clean:
(I am creating the home instance in the feature test, since I need a home to be selected in the form for the product. This house belongs to other models)
require 'rails_helper'
require 'pry'
RSpec.describe 'Add a product features' do
context "Create new product from add a product menu" do
let(:user) { create(:user) }
let!(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
# home.name = "My place"
# home.save
end
before(:each) do
# binding.pry
login_as(user, :scope => :user)
visit menu_add_product_path
click_link("Take a picture")
expect(current_path).to eql('/products/new')
binding.pry
within('form') do
attach_file('product_taken_photos_attributes_0_taken_pic', File.absolute_path('./app/assets/images/macbook1.jpg'))
fill_in 'Brand', with: "Apple"
fill_in 'Product type', with: "Smartphone"
fill_in 'Price of purchase', with: 800.3
fill_in 'Date of purchase', with: "2017-05-03"
select("My place", :from => 'product_home_id')
end
end
it 'should be successful' do
binding.pry
within('form') do
fill_in 'Model', with: "Iphone 6"
end
click_button('Create Product')
binding.pry
expect(current_path).to eql(product_path(Product.last))
expect(page).to have_content 'Iphone 6'
end
# it 'should not be successful' do
# click_button('Create Product')
# expect(current_path).to eql('/products') # the post products actually!
# expect(page).to have_content(/Model can\'t be blank/)
# end
end
end
Factories:
home.rb
FactoryBot.define do
factory :home do
sequence(:name) { |n| "My Home#{n}" }
address 'c/ Viladomat n200 50 1a'
insurer
house_type
user
end
end
product.rb
FactoryBot.define do
factory :product do
model 'macbook pro'
form_brand 'apple'
form_product_type 'laptop'
price_of_purchase 1200
date_of_purchase Date.new(2017,06,06)
end
end
user.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "myemail#{n}#mail.com" }
password 123456
end
end
house_type.rb
FactoryBot.define do
factory :house_type do
name 'Flat'
end
end
If I use the let! operator to create a home for all the tests, the test fails:
let!(:home) { create(:home, name: "My Place", user: user) }
Console log:
Capybara::ElementNotFound:
Unable to find visible option "My place" within #<Capybara::Node::Element tag="select" path="/html/body/div[2]/form/div[4]/div/div[2]/select">
But, if I create the home manually, before each test, it works
let(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
home.name = "My place"
home.save
end
Why is the let! not working? If I put a binding.pry in my test, in both cases I have the created home in my database.
You should be configuring your factories to automatically create needed default associations so you can create a needed instance in your tests without having to create all the other non-specialized records. Your home factory should look something like
FactoryBot.define do
factory :home do
sequence(:name) { |n| "Home#{n}" }
address { 'c/ Viladomat n200 50 1a' } # You might want to define this to use a sequence too so it's unique when you create multiples
insurer
house_type
user
end
end
Something like that would then let you create a valid Home instance by just calling create(:home). If you want to customize any of associations/parameters you can pass them to the factory create/build method. So in your example it would just become
let(:home) { create(:home, name: 'My place') }
If you wanted to also manually create the user object, so you can call login(user...) rather than having to access an auto generated user like login(home.user...) then you would do
let(:user) { create(:user) }
let!(:home) { create(:home, name: 'My place', user: user }
Note the use of let! for home rather than let. This is because let is lazily evaluated so the instance won't actually be built until you first call home in your test - Since, when calling login_as(user..., you don't call home in your test you need to use let! instead so the object is created before your test is run. You also probably want to be using FactoryBot sequences in things like the email of your user factory, so that you can create more than one user in tests.
Additionally you're calling expect(current_path).to eql('/new_from_camera'), which will lead to flaky tests since the eql matcher doesn't have waiting behavior built-in. Instead you should always prefer the Capybara provided matchers which would mean calling expect(page).to have_current_path('/new_form_camera') instead.
I think you can add the associations directly in the home factory:
let(:insurer) { create(:insurer) }
let(:house_type) { create(:house_type) }
let(:user) { create(:user) }
let(:home) { create(:home, name: "My place", insurer: insurer, house_type: house_type, user: user) }

Why does this spec of my model's uniqueness validation fail when it should pass?

I am learning testing with RSpec. Something is not working with my tests.
My model:
class User < ActiveRecord::Base
has_secure_password
# Validation macros
validates_presence_of :name, :email
validates_uniqueness_of :email, case_sensitive: false
end
My factory:
FactoryGirl.define do
factory :user do
name "Joe Doe"
email "joe#example.com"
password_digest "super_secret_password"
end
end
And my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
it 'has a valid factory' do
expect(FactoryGirl.build(:user)).to be_valid
end
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:email) }
it { is_expected.to respond_to(:password) }
it { is_expected.to respond_to(:password_confirmation) }
it { expect(user).to validate_presence_of(:name) }
it { expect(user).to validate_presence_of(:email) }
it { expect(user).to validate_presence_of(:password) }
it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
end
I expected this test to pass. But I get this as a result:
Failures:
1) User should validate that :email is case-insensitively unique
Failure/Error: it { expect(user).to validate_uniqueness_of(:email).case_insensitive }
User did not properly validate that :email is case-insensitively unique.
The record you provided could not be created, as it failed with the
following validation errors:
* name: ["can't be blank"]
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
Finished in 0.34066 seconds (files took 1.56 seconds to load) 9
examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:18 # User should validate that :email
is case-insensitively unique
What I am missing?
Update
I think that this is a bug: https://github.com/thoughtbot/shoulda-matchers/issues/830
It is because you are declaring it 2 times IMO! First building user then building same user inside expect().
Just use ur first user that you have built with factory-bot like so:
it 'has a valid factory' do
expect(user).to be_valid
end
P.S
It is better to use Faker gem instead of using harcoded instances like you did in factory.rb
Your Variable Is Currently Only Set Once for All Tests
When you write code like:
RSpec.describe User, type: :model do
user = FactoryGirl.build(:user)
end
you aren't building a new user each time you run a new spec. Likewise, using #let is the wrong approach, because it memoizes the variable even between tests. Instead, you need a to use an RSpec before#each block. For example:
describe User do
before do
#user = FactoryGirl.build :user
end
# some specs
end
If you have tests which are persisting you user to the database, and if you have disabled rollback or database cleaning between tests, then your defined factory (as currently written) will certainly fail the uniqueness validation. In such cases, you may want to try:
User.delete_all in your test, or otherwise cleaning your database between tests.
Using FactoryGirl sequences or the Faker gem to ensure that user attributes are actually unique.
USE let
RSpec.describe User, type: :model do
let(:user) { FactoryGirl.build(:user) }
# other what you need

Why does my test return a nil class error on an attribute it shouldn't?

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.

FactoryGirl creating multiple records

I'm trying to get in the habit of writing specs, however, this is becoming increasingly frustrating.
Assume I have two simple models: User and Story. Each model uses a belongs_to relation. Each model uses a validates :foo_id, presence: true as well.
However, FactoryGirl is creating multiple records.
FactoryGirl.define do
factory :user do
email "foo#bar.com"
password "foobarfoobar"
end # this creates user_id: 1
factory :story do
title "this is the title"
body "this is the body"
user # this creates user_id: 2
end
end
This simple test fails:
require 'rails_helper'
describe Story do
let(:user) { FactoryGirl.create(:user) }
let(:story) { FactoryGirl.create(:story) }
it 'should belong to User' do
story.user = user
expect(story.user).to eq(user)
end
end
What am I missing here? I cannot build a Story factory without a User, yet I need it to be just one User record.
The values you define for each attribute in a factory are only used if you don't specify a value in your create or build call.
user = FactoryGirl.create(:user)
story = FactoryGirl.create(:story, user: user)
When doing something like this you can do:
let(:story) { FactoryGirl.create(:story, user: user) }
Or maybe you can only let the story variable and do:
let(:story) { FactoryGirl.create(:story, user: user) }
let(:user) { User.last}
Yes, it is a feature of factory girl to create the associated user when you create the story.
You can avoid it like this:
require 'rails_helper'
describe Story do
let(:story) { FactoryGirl.create(:story) }
let(:user) { story.user }
it 'should belong to User' do
story.user.should eq user
end
end
This example is setup to be trivially true, but you get the point.

Factory girl not initiating object

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

Resources