I'm a newcomer when it comes to both Capybara FactoryGirl, In my Rails app I have a relationship that looks like this:
# App.rb
belong_to :plan
# Plan.rb
has_many :apps
Each app must have a plan, in my App.rb model I do it like this: before_save :set_default_plan, on: :create.
I want to test that app creation works, using Capybara integration tests. I currently have a test that looks like this:
require "rails_helper"
include Warden::Test::Helpers
Warden.test_mode!
describe "adding apps" do
let(:user) { FactoryGirl.create(:user) }
before { login_as(user, scope: :user) }
it "allows a user to create an app" do
visit apps_path
fill_in "App name", with: "My App"
click_on "create_app_button"
visit apps_path
expect(page).to have_content("My App")
end
end
After I create apps I render this in my view: #{app.plan.free_requests}. If I run my tests with bundle exec rspec I currently get this error:
undefined method `free_requests' for nil:NilClass
In my app I also use FactoryGirl to test my models. I have the following (relevant) factories:
FactoryGirl.define do
factory :app do
name "Test"
[...]
association :plan, :factory => :plan
end
end
FactoryGirl.define do
factory :plan do
name "Default"
[...]
end
end
I wonder how I should set ut my factories and test suite to make this test become a green one.
Can I either assign a plan to the app I'm creating somehow with Capybara, or can I create a default association / plan for my app with FactoryGirl. Is is there another approach? Thankful for all assistance.
Update
This is how my set_default_plan method looks:
# App.rb
def set_default_plan
if self.new_record?
plan = Plan.find_by_stripe_id("default_plan")
if plan.nil? == false
self.plan = plan
end
end
end
FactoryGirl really shouldn't have anything to do with "apps" or "plans" in your test, since you're running through your controllers create action, unless set_default_plan doesn't actually create a plan if none exist. If that is the case then you could use FactoryGirl to create the required plan like - FactoryGirl.create(:plan) in your before block
You should also specify that the plan is a required association (this is default in Rails 5 so if you're using that this may not be necessary) which will prevent your Apps from being created without a plan.
# App.rb
belongs_to :plan, required: true
Another thing to note is you should always check for confirmation after clicking a button that performs an action before visiting another page. This is because the result of clicking the button is not guaranteed to be synchronous so visiting another page immediately can kill the action request.
click_on "create_app_button"
expect(page).to have_content("App Created!!!") # whatever text is shown on success
visit apps_path
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 have a model called "availabilities" in my Rails application that allows vendors to set their availability (i.e. their work hours). Thus, availabilities belong_to vendors and belong_to users, and a user has_many vendors and a vendor has_many availabilities.
I've been trying to create Rspec tests for my availability#destroy action. The specific test I refer to is:
#spec/controllers/availabilities_controller_spec.rb
require 'rails_helper'
RSpec.describe AvailabilitiesController, type: :controller do
describe "availabilities#destroy action" do
it "should allow a user who created the availability to destroy it"
availability = FactoryBot.create(:availability)
sign_in availability.user
delete :destroy, params: { id: availability.id, vendor_id: availability.vendor_id}
availability = Availability.find_by_id(availability.id)
expect(availability).to eq nil
end
end
end
When I run this test however, I receive the following error:
"An error occurred while loading ./spec/controllers/availabilities_controller_spec.rb.
Failure/Error: user = FactoryBot.create(:user)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken"
However, I use factory bot for my factories, and I have my user factory to run as a sequence (see below):
FactoryBot.define do
factory :user do
sequence :email do |n|
"dummyEmail#{n}#gmail.com"
end
password "secretPassword"
password_confirmation "secretPassword"
confirmed_at Time.now
end
end
How can the email already be taken? What could explain this error?
I recommend you to use Faker along with FactoryBot. It will give your more flexibility and eliminate the need to do this sequence trick. Faker generates fake data with ease.
In any way, use database_cleaner to clean your test environment database after each test. You'll only need to set this is up:
# ./spec/rails_helper.rb
# start by truncating all the tables but then use the faster transaction strategy the rest of the time.
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
DatabaseCleaner.strategy = :transaction
end
# start the transaction strategy as examples are run
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
I added an import method to a controller, and it works just fine when I test it manually from my website, but it is failing in rspec. Here is what my test looks like:
require 'spec_helper'
describe PropertiesController do
let!(:user) { FactoryGirl.create(:user) }
before :each do
sign_in user
end
describe "should upload user properties" do
before do
post :import, spreadsheet: fixture_file_upload("/files/property_upload_template.xlsx")
end
it "should have created records" do
expect(Property.count).to eq 3
# Some other assertions
end
end
end
When I add puts statements inside my import action, including on the very first line, none of them are apparently invoked. The test is generating no errors other than failing the assertions. Similarly, when I look at the test.log file, all that happens is the creation of my test user (and a devise confirmation email gets sent out), but it doesn't appear that the import action is ever hit. The test server seems to recognize the route fine, but it's not actually executing the action.
Is there something wrong with my test configuration?
I had been banging my head for a good couple hours, but I just figured it out. I needed to confirm the user in my user factory. I guess since I enabled the confirmable module in devise, and the user wasn't confirmed, it was silently not allowing me to authenticate...
... Would sure be nice if rspec/rails/devise generated some sort of error pointing me to the problem here.
For the sake of completeness, I'm adding in the code for confirming a user in the version of FactoryGirl at the time of that writing:
FactoryGirl.define do
factory :confirmed_user, :parent => :user do
after(:create) { |user| user.confirm! }
end
end
This should be very simple to test, but for some reason my test is failing, please consider the following, model bit:
class User < ActiveRecord::Base
def active!
update_attribute(:active, true)
end
end
controller :
def activate
user = User.find_by_uuid(params[:id])
user.active!
puts "id #{user.id}"
end
test :
describe 'activate user' do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
visit activate_user_path(id: user.uuid)
puts "id #{user.id}"
end
it 'should be true' do
save_and_open_page
user.active.should be_true
end
end
This test fails :
expected: true value
got: false
But when I do it with browser, the user gets activated without problems. What am I doing wrong? This really looks like a sily test but still doesn't pass, I've spend more than one hour trying out different stuff, none of which worked.
The problem is that the spec still holds the User in the user variable that was created via FactoryGirl and does not know that is was changed in the database. Just reload the user and it should work:
it 'should be true' do
save_and_open_page
user.reload.active.should be_true
end
Btw. if active is a boolean you can also spec it this way (what reads much nicer):
user.reload.should be_active
Given the following simple spec:
require 'spec_helper'
feature 'Feeds', %q{
In order see the latest content of Buurtlink
As a user
I should be able to view a neighborhood or postal_code
} do
background do
#neighborhood = FactoryGirl.create(:neighborhood_with_posts)
end
scenario 'Visitor views a neighborhood' do
visit neighborhood_path(#neighborhood)
find('#header_title').should have_content 'Diemen-Zuid'
10.times do |index|
expect(page).to have_text "Title of new post #{index}"
end
end
end
This test randomly fails. No JS is used on the page, but Capybara seems to visit the neighborhood_path before FactoryGirl is done creating all necessary posts. When looking at the page using save_and_open_page I can see that sometimes not all posts have been created yet.
Simply adding sleep 1 above visit neighborhood_path fixes the problem, but that's not a solution.
I'm using RSpec, Capybara, Spork and DatabaseCleaner. I also monkey-patched ActiveRecord so that it uses a shared connection.
Try this instead of your background block:
let!(:neighborhood){FactoryGirl.create(:neighborhood_with_posts)}
and you can do
visit neighborhood_path(neighborhood)
This was in the ApplicationController:
def load_posts_after_current_time
session[:load_posts_after] = DateTime.now unless params[:page].present?
end
Therefore, errors occurred when fetching newly created records. So added created_at 1.hour.ago to the post factory.
factory :post do
user { FactoryGirl.create(:user) }
created_at 1.hour.ago
factory :neighborhood_post do
association :postable, factory: :_neighborhood_post
end
end
It works!