The following Capybara test...
require 'rails_helper'
feature 'Sheet owner' do
scenario 'logs in, finds sheet and adds a row' do
sheet = create :sheet
user = create :user
visit login_path
fill_in('Email', :with => user.email)
fill_in('Password', :with => user.password)
click_button('Log in')
end
end
... results in an error:
Failures:
1) Sheet owner logs in, finds sheet and adds a row
Failure/Error: click_button('Log in')
ActionView::Template::Error:
no implicit conversion of nil into String
Yet passes when I remove sheet = create :sheet.
My sheet factory:
FactoryGirl.define do
factory :sheet do
organization "Organization"
event "Event"
description "Description"
location_id 1
user_id 1
misc_dates "1"
end
end
Any ideas why this factory is breaking this test?
In terms of my models, users has_many sheets.
Perhaps you are getting an error that prevents click_button from having access to the "Log In" button. It may be looking for it on an error page.
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'm creating a bit of code where a user creates Sales Opportunities, which are associated with a certain company. I set up some code that was working fine when a user had to manually input the id of the company, then changed it so the form would display a list of all companies associated with that user's organization (user belongs_to organization, company belongs_to organization, sales_opportunity belongs_to both User and Company).
This has caused my Rspec/Capybara tests to fail with the following error message:
Failure/Error: page.select "Test Co", :from => "Company"
Capybara::ElementNotFound:
Unable to find option "Test Co"
The relevant tests:
describe "sales opportunities" do
let(:organization) { FactoryGirl.create(:organization, :name_one, :with_users)}
let(:company) {organization.companies.create(company_name: "Test Co", organization_id: organization.id, existing_customer: true)}
let(:user) {organization.users.first}
before do
sign_in user
visit user_path(user)
end
it 'has links to add a new sales opportunity' do
expect(page).to have_link('Add sales opportunity', href: new_user_sales_opportunity_path(user_id: user.id))
end
it 'adds a new sales opportunity' do
page.click_link('Add sales opportunity')
page.fill_in('Opportunity name', with: "Capybara Opportunity")
page.fill_in('Close date', with: "2014/12/18")
page.fill_in('Sale value', with: 20000)
page.select "Test Co", :from => "Company"
page.click_button('Save')
expect(current_path).to eq(user_path(user))
expect(page).to have_content('Capybara Opportunity')
expect(page).to have_content('20000')
expect(page).to have_content('2014-12-18')
end
The form field for selecting a company:
<%= f.label :company_id %><br>
<%= f.collection_select :company_id, #user.organization.companies(:company_name), :id, :company_name %>
I can include other parts of the code if you think they're necessary, but from my current guess it seems the Company I'm creating with the "Let" block is not associated with my Organization/User, OR the form is not able to identify this company for some reason. I can't quite work out what I'm doing wrong here - can you help please?
OK, so I fixed this - it was related to the way I was building the company in the test suite. Instead of doing it as above, I put it into a Factory instead:
FactoryGirl.define do
factory :organization do
trait :name_one do
organization_name "New Example Org"
end
trait :name_two do
organization_name "Any Wrong Org"
end
trait :with_users do
before(:create) do |organization|
organization.users << FactoryGirl.build(:user)
organization.users << FactoryGirl.build(:user, name: "Second User", email: "email2#example.com")
end
end
And:
factory :company do
company_name "Test Company"
existing_customer "False"
association :organization, :name_one, :with_users
end
And then called it as follows in the rspec test:
describe "sales opportunities" do
let(:company) {FactoryGirl.create(:company)}
let(:user) {company.organization.users.first}
before do
sign_in user
visit user_path(user)
end
it 'has links to add a new sales opportunity' do
expect(page).to have_link('Add sales opportunity', href: new_user_sales_opportunity_path(user_id: user.id))
end
it 'adds a new sales opportunity' do
page.click_link('Add sales opportunity')
page.fill_in('Opportunity name', with: "Capybara Opportunity")
page.fill_in('Close date', with: "2014/12/18")
page.fill_in('Sale value', with: 20000)
page.select "Test Company", :from => "Company"
page.click_button('Save')
expect(current_path).to eq(user_path(user))
expect(page).to have_content('Capybara Opportunity')
expect(page).to have_content('20000')
expect(page).to have_content('2014-12-18')
end
This sends all the tests green, so whilst I'm not sure what the hell I was doing wrong initially, the answer is to build using FactoryGirl.
I have 2 models: Venue and Event. Venue has_many :events and Event belongs_to :venue.
My events/new form has the following fields
Date (populated with current date)
Time (populated with current time)
Title (empty)
Venue (populated with venues table from development database)
Now I'm trying to test this with RSpec and Capybara. Here's my feature:
require 'spec_helper'
feature "Events" do
let(:venue) { FactoryGirl.create(:venue) }
let(:event) { FactoryGirl.create(:event, venue: venue) }
scenario "Creating event as guest" do
visit root_path
click_link "Submit Event"
fill_in "Title", with: event.title
click_button "Submit"
expect(page).to have_content("Event has been created.")
expect(page).to have_content(event.title)
end
end
My factories look like this:
FactoryGirl.define do
factory :event do
date "2014-02-27"
time "10:00am"
title "Slayer"
end
end
FactoryGirl.define do
factory :venue do
name "Example Venue"
url "http://www.example.com"
address "123 Main St., Chicago, IL"
end
end
When I run this spec I get the following error:
Failure/Error: click_button "Submit"
ActionView::Template::Error:
Couldn't find Venue without an ID
I'm guessing this has something to do with the fact that the Venue field is not populated in the test environment?
using let is lazy, so your database call will get executed here:
scenario "Creating event as guest" do
visit root_path
click_link "Submit Event"
fill_in "Title", with: event.title # <------------------- evaluated!
click_button "Submit"
expect(page).to have_content("Event has been created.")
expect(page).to have_content(event.title)
end
what you probably want is this:
feature "Events" do
background do
#event = FactoryGirl.create(:event, venue: FactoryGirl.create(:venue))
end
scenario "Creating event as guest" do
visit root_path
click_link "Submit Event"
fill_in "Title", with: #event.title
click_button "Submit"
expect(page).to have_content("Event has been created.")
expect(page).to have_content(#event.title)
end
end
you could also use let! which executes directly, but i think the setup block is much clearer.
I have two capybara tests that are testing the signup process on my rails application, both using factory girl. One is just using Factory Girl build command and saving it with the form:
it 'should create a user and associated customer_info', js: true do
visit signup_path
user = build(:user)
customer = build(:customer_info)
sign_up user, customer
page.should have_content 'Welcome back, ' + customer.firstname
end
Whereas the other is using the create command, and then attempting to sign in with that info.
it 'should be able to sign in', js: true do
user = create(:user)
customer = create(:customer_info, user_id: user.id)
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_button 'Sign in'
page.should have_content 'Welcome back, ' + customer.firstname
end
The first one passes and saves in my test database. The second one fails, saying "invalid email or password," but also when I check my database after each test, the first one saves a record but the second one doesn't (which I'm assuming is why it's saying invalid email/password).
Any ideas why my FactoryGirl create function isn't actually saving my record in the database?
EDIT
I have a sequence in my FactoryGirl definition for the email, and build AND create both increase the sequence, so it shouldn't be creating dups, right?
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "foo#{n}#example.com"}
password "secret"
password_confirmation "secret"
end
end
The problem is you are trying to create duplicate user. Sign up creates user in test database and now when you are trying to create a new user with FactoryGirl it would raise validation error because the same user is already there in test database. You should do something like this:
def create_user
#user ||= create(:user)
end
it 'should create a user and associated customer_info', js: true do
visit signup_path
#user = build(:user)
customer = build(:customer_info)
sign_up #user, customer
page.should have_content 'Welcome, ' + customer.firstname
end
it 'should be able to sign in', js: true do
create_user
customer = create(:customer_info, user_id: #user.id)
visit new_user_session_path
fill_in 'user_email', with: #user.email
fill_in 'user_password', with: #user.password
click_button 'Sign in'
page.should have_content 'Welcome back, ' + customer.firstname
end
May be you can use the different approach to solve it. but main focus is on to use the single user object for sign up and sign in.
Hope this would help you.
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