I am learning to do tests using Rails testing tools, mainly: rspec, factory_girl, shoulda, and faker gems.
I want to test has_one and belongs_to associations. I have user.rb (with name) and address.rb (with street, city, and user_id) models.
I am trying to figure out how to create fake associations using fakers. I want to create a fake street and associate it with a fake user, but I can't figure out how to do it on spec/factories/addresses.rb
This is what I currently have:
FactoryGirl.define do
factory :address do
street {Faker::Address.street_address}
city {Faker::Address.city}
#Creates fake user and use that user's id for user_id)#
user = build(:user, name: "Joe", id: 2)
user_id {user.id}
end
end
This is the spec/models/address_spec.rb
require 'rails_helper'
RSpec.describe Address, type: :model do
it "has a valid factory" do
address = build(:address)
expect(address).to be_valid
end
it {should belong_to(:user)}
end
When I run it, it shows add_attribute': wrong number of arguments (given 3, expected 1..2) (ArgumentError) error. I tried removing the id on the argument and just did user = build(:user, name: "Joe") but the same error message still shows.
The test passes if I don't include the user lines on addresses factory. I confirmed that address has belongs_to :user and user has has_one :address.
Is it possible to generate fake association and associate address to a fake user.id? I would like to test Address's user_id as well.
Edit - following are the related factories and models:
Factories
addresses.rb
FactoryGirl.define do
factory :address do
street {Faker::Address.street_address}
city {Faker::Address.city}
user
end
end
users.rb
FactoryGirl.define do
factory :user do
name {Faker::Name.name}
end
end
spec/models
address_spec.rb
require 'rails_helper'
RSpec.describe Address, type: :model do
it "has a valid factory" do
association user
specific_user = build(:user, name: 'Joe')
address = build(:address, specific_user)
expect(address).to be_valid
end
it {should belong_to(:user)}
end
Error running address_spec.rb
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::Address:0x00000001a94b00>
# ./spec/models/addres
I think the way you build association is incorrect. See https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations.
If you want a more specific associated user eg name: Joe, id: 2, it's probably better to do it in spec.
Factory:
FactoryGirl.define do
factory :address do
street {Faker::Address.street_address}
city {Faker::Address.city}
user
end
end
Spec:
it "has a valid factory" do
specific_user = build(:user, name: 'Joe')
address = build(:address, specific_user)
expect(address).to be_valid
end
Related
I've got a problem with Factory bot and logging in as a designated user. I'm trying to run a simple Edit test in rspec. Here it is:
require "rails_helper"
RSpec.describe "Treat management", :type => :system do
before do
treat = FactoryBot.create(:treat)
user = build(:user, email: 'wojtek#gmail.com', password: 'password')
login_as(user)
driven_by(:selenium_chrome_headless)
end
it "enables me to edit treats" do
visit root_path
click_button 'Edit'
fill_in 'Name', with: 'A new name'
fill_in 'Content', with: 'A new content'
click_button "Update Treat"
expect(page).to have_text("Treat was edited successfully")
end
end
And here is my Treat factory. Treats have a name, content and a giver and a receiver foreign keys
FactoryBot.define do
factory :treat do
name {'my first factory treat'}
content {'this is my first treat created by a factory'}
giver factory: :user
receiver factory: :user
end
end
And of course the user factory. Users are defined by email and password
FactoryBot.define do
factory :user do
email {Faker::Internet.email}
password {'password'}
end
end
And you have to know the edit buttom is only present when the logged user is also the giver. I have asked around and supposedly my Treat factory is is well configured. Please help me solve this. If any other parts of code are required please let me know in comments and I'll update accordingly. And of course I know that there is a simplier way to write this test but the use of the factories is a requirement.
1
I have tried hardcoding the user in the factory (without the Faker gem) but that trigers the validation error - the email has been taken.
Right now FactoryBot.create(:treat) will create a User for giver and User for receiver based on the Factory definition.
FactoryBot.define do
factory :treat do
name {'my first factory treat'}
content {'this is my first treat created by a factory'}
giver factory: :user # tells the factory to create a User from the User Factory
receiver factory: :user # tells the factory to create a User from the User Factory
end
end
You are calling this in your test but then creating a third user to test with
before do
treat = FactoryBot.create(:treat) # 2 users created
# changed to `create` since as #max pointed out `build` does not actually create a `User`
user = create(:user, email: 'wojtek#gmail.com', password: 'password') # third user
end
This third user is neither the giver or receiver of the Treat which is why your test fails.
Instead you can override definitions in the Factory by passing arguments to create. In this case you want the User object under test to be the giver of the Treat so we can achieve this as follows (I used modified version of #max's test scheme as it is the preferred way to set this up)
require "rails_helper"
RSpec.describe "Treat management", type: :system do
let(:user) { create(:user) }
before do
driven_by(:selenium_chrome_headless)
end
context 'A Treat#giver' do
let!(:treat) {create(:treat, giver: user)}
before do
login_as(user)
end
it "can edit Treats they've given" do
visit root_path
click_button 'Edit'
fill_in 'Name', with: 'A new name'
fill_in 'Content', with: 'A new content'
click_button "Update Treat"
expect(page).to have_text("Treat was edited successfully")
end
end
end
Here we replace the default creation of a "giver" user with the specific user returned by user method defined in the let block. This ensures that user == treat.giver so that your test can succeed.
When I am trying to run test case for model using Rspec and Factory girl it is showing uninitialized constant Wing
code in factories/wing.rb
FactoryGirl.define do
factory :wing do
wing_name "Example Title"
is_deleted "0"
mg_school_id "1"
created_by "2013-06-02 02:28:12"
updated_by "2013-06-02 02:28:12"
end
end
code in model/mg_wing_spec.rb
require 'rails_helper'
RSpec.describe MgWing, type: :model do
it "has a valid factory" do
#hai=FactoryGirl.create(:wing)
end
end
Change filename factories/wing.rb to factories/mg_wing.rb
Your factory name should be the same in your model and rspec filename.
Here are a couple of tutorials if you want it:
https://medium.com/#JonoYeong/setting-up-rspec-and-factory-girl-8cf287801099
https://semaphoreci.com/community/tutorials/working-effectively-with-data-factories-using-factorygirl
You either need the factory name same as the model name or pass the class param and factorygirl will take care of it
Solution 1
FactoryGirl.define do
factory :mg_wing do
...
end
end
Solution 2
FactoryGirl.define do
factory :wing, class: MgWing do
...
end
end
I've integrated Devise with my RoR app and am now trying to test my Controllers, specifically the one that routes me to my root_url.
I've used this HOWTO on Devise's page to setup my admin/user Factories, but there is an additional component that is part of my user signup process, which is creating a Company.
So:
User: has_one :company
Company: has_many :users
The flow for a new user looks like this:
User signs up
User confirms account (via email) and is redirected to the login page
User logs in
User fills out Company information and submits
User is then redirected to Pages#home (which is my root_url)
Using Devise's HOWTO, I created a ControllerHelpers file within Support:
module ControllerHelpers
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
end
I suspect there is something wrong with my User Factory since it doesn't seem like a Company is being created, but I'm so new to RSpec, I'm unsure.
FactoryGirl.define do
factory :user do
first_name "Test"
last_name "User"
full_name "Test User"
email "test#user.com"
phone_number "111-222-3333"
terms_accepted true
time_zone "Central Time (US & Canada)"
password "password"
password_confirmation "password"
confirmed_at Date.today
association :company
end
end
And I have a company.rb factory as well:
FactoryGirl.define do
factory :company do
id 1
name "ACME Test"
address_1 "123 Shady Lane."
address_2 "Suite 400"
city "Testville"
state "Test"
zip_code "12345"
has_payment_plan false
stripe_id "cus_34d434343e4e3e3"
locked false
end
end
My pages_controller_spec.rb is simple at this point:
require 'rails_helper'
RSpec.describe PagesController, :type => :controller do
describe "User: GET #home" do
login_user
it "signs in the user" do
expect(response).to render_template(:home)
end
end
end
This results in the following RSpec error:
1) PagesController User: GET #home signs in the user
Failure/Error: expect(response).to render_template(:home)
expecting <"home"> but was a redirect to <http://test.host/companies/new>
# ./spec/controllers/pages_controller_spec.rb:10:in `block (3 levels) in <top (required)>'
So, it's not even doing the render_template portion of my test?
UPDATE: Added Home Controller
controllers/pages_controller#home
def home
if current_user && current_user.company
verify_subscription
get_company_and_locations
get_network_hosts
get_network_hosts_at_risk
#network_hosts_snip = #network_hosts_at_risk.sort_by{ |h| -h.security_percentage }.first(5)
get_company_issues
#issues = #issues.sort_by{ |i| -i.cvss_score }.first(5)
#deferred_issues = #company.deferred_issues.last(5)
#deferred_hosts = #company.deferred_hosts.last(5)
else
redirect_to new_company_path
end
end
As we found out together in the chat... Your company was created but wasn't persisted to DB. It's because you had strategy: :build in your factory.
strategy: :build means that your association object will be created but won't be persisted to DB. To persist it you should use strategy: :create. Or in your use case you can replace association :company, strategy: :build with just company. FactoryGirl is smart enough to recognize it as an association which must be created and persisted.
And you need to set up FactoryGirl association between company and subscription the same way.
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
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.