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.
Related
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) }
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
I am learning how to test on rails from this tutorial.
On one part of the tutorial, it shows how to write invalid_attribute test:
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
describe "POST #create" do
context "with valid attributes" do
it "create new contact" do
post :create, contact: attributes_for(:contact)
expect(Contact.count).to eq(1)
end
end
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(:invalid_contact)
expect(Contact.count).to eq(0)
end
end
end
end
I don't understand where :contact and :invalid_contact point to.
Does :contact points to Contact class? It seems like it from FactoryGirl's gh. If so, then how can I create :invalid_contact since there is no :invalid_contact class?
I have tried post :create, contact: attributes_for(:contact, :full_name => nil) but it still fails.
spec/factories/contacts.rb:
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
end
end
First test, with valid attributes pass. On model, there is presence validation validates_presence_of :full_name, :email, :phone_number, :address. What do I add in order to pass "with invalid attributes" test?
The factory will use the class with the same name. So your :contact factory will use the Contact class. You can create a new factory for the invalid contact by specifying the class to use.
factory :invalid_contact, class: Contact do
full_name nil
end
It's also possible to use traits to avoid having two different factories.
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
trait :invalid do
full_name nil
end
end
end
Then use it with attributes_for(:contact, :invalid)
The tutorial you link to says:
Following the spec above, write a spec that uses invalid attributes to
create a new contact. This spec should check that the contact is not
created.
So you need to figure out how to test for :invalid_contact using the example for :contact.
You can just add a let in your spec:
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Source: https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let
Then your controller spec would look like this:
...
let(:invalid_contact) { create(:contact, name: nil) }
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(invalid_contact)
expect(Contact.count).to eq(0)
end
end
...
this way #post action params are picked up from invalid_contact
or as #fanta suggested in comments, you can add a trait to your factory. I prefer my method because other people looking at your code will know why invalid_contact should be invalid without looking at the :contacts factory
In my app I have following models:
class UserApplication < ActiveRecord::Base
has_many :builds, dependent: :destroy
belongs_to :user
end
class Build < ActiveRecord::Base
belongs_to :user_application
end
And User model is generated by Devise.
My question is: is I want to test, say, model validations, should I do it like this:
require 'spec_helper'
describe UserApplication do
it "is invalid without name" do
user_application = UserApplication.new(name: nil)
expect(user_application).to have(1).errors_on(:name)
end
end
Or should I create UserApplication through User? In general, should I bear in mind associations when testing my models, if test example is not connected to relationships?
It seems prudent to have test code parallel app code as closely as possible. That is, if UserApplication will be created via User in the controller, it ought to be done the same way in the test. Furthermore, your UserApplication validations will probably test the association sooner or later anyway, so the test subject should be created in such a way as to be valid. With that in mind, you can set up your tests as follows:
require 'spec_helper'
describe UserApplication do
let(:user) { User.create(user_params) }
before { #user_application = user.user_applications.build(name: 'name') }
subject { #user_application }
describe 'validations' do
context 'when name is missing' do
before { #user_application.name = '' }
it { should_not be_valid }
end
context 'when user_id is missing' do
before { #user_application.user_id = nil }
it { should_not be_valid }
end
# other validations
end
end
You should test validations/associations/fields existence/etc in corresponding specs. I don't see your UserApplication validations, but if I correctly understand you, your specs for this model should look like this(I am using shoulda and shoulda-matchers gems for testing):
require 'spec_helper'
describe UserApplication do
let!(:user_application) { create(:user_application) }
it { should have_many(:builds).dependent(:destroy) }
it { should belong_to(:user) }
it { should validate_presence_of(:name) }
end
I am always creating only the instance of the model I want to test. It is important to test that associations exist and correct, but you don't need to create testing model instance through association.
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