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
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.
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) }
So I want to make sure this is possible and actually doable with what im trying to do.
I had previously been using a seeds.rb file to seed the database with test data, this worked for my Capybara integration tests but messed up some of the other unit tests which rely on a schema.rb file and Factories to create data.
Can I use a factory to create test data for that particular integration spec. Something like this for a factory:
FactoryGirl.define do
factory :user do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
email Faker::Internet.email
password "password"
end
end
Then I have a super simple spec which logins to a site in Capybara:
it 'Check correct login' do
visit('/sign_in')
page.fill_in 'user_email', :with => 'test#test.com'
page.fill_in 'user_password', :with => 'p4ssw0rd'
click_button('Sign In')
end
This is hitting a Sqlite database, can I insert my factory and it will work using capybara? Im really not familiar with factorygirl, but is it actually creating data in the database and then just removing it?
I don't commonly see Capybara used with FactoryGirl in a lot of the examples I search online, is there a reason for that?
edit:
Here is what I have currently:
it 'Check correct login' do
user = FactoryGirl.create(:user)
visit('/d/users/sign_in')
page.fill_in 'user_email', :with => user.email
page.fill_in 'user_password', :with => user.password
click_button('Log In')
page.assert_text('Signed in successfully.')
end
I was able to take a screenshot and confirm that the forms are getting filled in with a random username/password, but using DB Browser for SQlite I don't seem to see the actual data in the database getting populated (Which might be difficult since once the spec is done it's deleted right?) but I can't tell if it's actually creating the data or not.
Thanks!
This should work fine w/o many hitches. You'll need to instantiate your User object in your test as this looks like a 'sign-in'.
You can create the object a couple of different ways.
(1)
describe 'Test' do
let(:user){ create :user }
...
it 'Specific Test' do
...
Note that let!(:user){...} creates immediately and let(:user){...} will only create user when the user is called. You will then be able to access user in all your tests.
(2)
describe 'Test' do
before do
#user = FactoryGirl.create( :user )
end
it 'Specific Test' do
...
Here you will be able to access #user in all your tests.
Let us know if that works and if not what error you're getting.
EDIT:
If your FG file looked like this:
FactoryGirl.define do
factory :user do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
email Faker::Internet.email
password "password"
work
end
end
You could associate work like this:
let(:user){ create :user, work: 'Some Place' }
#user = FactoryGirl.create(:user, work: 'Some Place')
I have a problem understanding where a specific part of code in MHartls tutorial comes from (and because of that I can't seem to change it to better fit my needs...) The part I am talking about is an rspec test:
describe "delete links" do
it { should_not have_link('delete') }
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
visit users_path
end
it { should have_link('delete', href: user_path(User.first)) }
it "should be able to delete another user" do
expect do
click_link('delete', match: :first)
end.to change(User, :count).by(-1)
end
it { should_not have_link('delete', href: user_path(admin)) }
end
Right now the test checks if a an admin attribute of a user is true or false. I am instead trying to check if the role attribute of a user contains the word 'Administrator'. The model is working, the views show everything correctly - but I don't know how to rewrite the test, right now it is failing.
One part that puzzles me specifically is "sign_in admin" and "user_path(admin)" - where does this come from? How does rspec know who the admin is?
And if it's an attribute that is somewhere defined, can I simply change it from admin = true/false to admin is true if role is administrator ?
Many thanks for your help!
Updated:
I do have a file called factories.rb, here is the content according to Mhartls tutorial:
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
I tried changing it into:
FactoryGirl.define do
factory :user do
sequence(:first_name) { |n| "First #{n}" }
sequence(:last_name) { |n| "Last #{n}" }
sequence(:primary_email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :role do
role "Administrator"
end
end
end
Additional information in response to your comments:
I am not using Devise - the user model is build based on the railstutorial with minor modifications (more form fields plus the admin field is in my case a field "role" with a string)
It is clear what user_path is - but why is there an (admin) behind it?
I am trying to test exactly what is in the test - the problem is that FactoryGirl tests with a user whose admin attribute is set to true.
Just to summarise:
I do not want to change what the test is testing - I only want to change how an administrator gets identified by factorygirl. Right now it tests if the attribute "admin" is set to true or false. I want it to check if the attribute "role" has the content "Administrator"
I think you may be confusing what you need to do with your test and what you need to do in your production code.
If you change the definition of FactoryGirl(:admin) as you have done, then your test is fine as is.
However, you need to change your production code so that "admin-ness" is defined in terms of role as well, and you haven't mentioned that.
Here's the test:
describe "admin attribute" do
before(:each) do
#user = User.create!(#attr)
end
it "should respond to admin" do
#user.should respond_to(:admin)
end
it "should not be an admin by default" do
#user.should_not be_admin
end
it "should be convertible to an admin" do
#user.toggle!(:admin)
#user.should be_admin
end
end
Here's the error:
1) User password encryption admin attribute should respond to admin
Failure/Error: #user = User.create!(#attr)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:128
I'm thinking the error might be somewhere in my data populator code:
require 'faker'
namespace :db do
desc "Fill database with sample data"
task :populate => :environment do
Rake::Task['db:reset'].invoke
admin = User.create!(:name => "Example User",
:email => "example#railstutorial.org",
:password => "foobar",
:password_confirmation => "foobar")
admin.toggle!(:admin)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}#railstutorial.org"
password = "password"
User.create!(:name => name,
:email => email,
:password => password,
:password_confirmation => password)
end
end
end
Please let me know if I should reproduce any more of my code.
UPDATE: Here's where #attr is defined, at the top of the user_spec.rb file:
require 'spec_helper'
describe User do
before(:each) do
#attr = {
:name => "Example User",
:email => "user#example.com",
:password => "foobar",
:password_confirmation => "foobar"
}
end
Check to be sure that there isn't a block further up your user_spec.rb that is calling User.create in a before(:each) block with the same email address. If your blocks are nested incorrectly, you'll get this error. For example, in the Rails tutorial, it's easy to accidentally nest your describe "admin attribute" inside your describe "password encryption" block, which uses the same before(:each) code.
Try checking for existing users in the before block:
before(:each) do
User.count.should == 0
#user = User.create!(#attr)
end
If that fails, then another user exists with the same email. This could be because another before block created a user with the same attributes, or that the test database was not correctly cleaned out after a failure. For the latter case, try running rake db:test:prepare, and then run the spec again.
before( :each ) is going to create a new user object from #attr. So if #attr isn't changing the values for its fields, and you have validations turned on to prevent duplicate, then on your 2nd test, the user object you created in the first test will collide with the one you are trying to create in the 2nd test.
There are other ways to go about testing your model without the database. For example, you can use test doubles to create and setup objects with exactly the data you want and then run your test to see if it behaves correctly. There is a [great book on RSpec, Cucumber and BDD] that could be a great source.
Edit: My apologies, I was confusing before(:each) with before(:all).
This does not seems to be ideal way of setting up test data. ie, using a rake task to populate the database.
A more standard unit testing and Rails practice would be to use both factory_girl or a test_fixture and transactional test fixture or database_cleaner gem.
Read a little bit about those, and they should be straight forward to use. They ensure, that each of your rspec test runs in isolation even when you run all of them together. That way, each test data for one test will not affect the other one.