This test clicks a link that hits show_fretboard in static_pages_controller.rb:
describe "StaticPages" do
subject { page }
let!(:key) { FactoryGirl.create(:key, user: user, name: 'C') }
.
.
.
describe "the fretBoard" do
before { visit root_path }
it 'should appear when clicked', js: true do
puts key.name
puts key.id
puts Key.find_by(name: 'C').id
click_link 'fretBoard-side-link'
page.should have_selector("#fretboard-key-header", :text => "Showing Notes in the Key of")
end
end
.
.
.
end
The test's failure shows this:
Run options: include {:line_numbers=>[141]}
C
9
9
An error occurred in an after hook
NoMethodError: undefined method `id' for nil:NilClass
occurred at /Users/user_name/rails_projects/my_project/app/controllers/static_pages_controller.rb:69:in `show_fretboard'
The key exists as it is outputting the key's name and ID. When static_pages_controller.rb tries to find the key, as seen here:
def show_fretboard
#key = params[:key] || Key.find_by(name: 'C').id
This error is thrown:
undefined method `id' for nil:NilClass
Why is this query not finding the Key object with name 'C'?
EDIT: This post (http://justinleitgeb.com/rails/common-rails-testing-mistakes/) says that:
Javascript-enabled tests in Capybara run in another thread
Database elements (from factories, etc) you create in the test example won’t be visible in the server thread
It looks like this is my issue, so my question becomes, how can I use JS tests to test my ajax calls while still querying the database.
EDIT: Database cleaner setup:
RSpec.configure do |config|
puts 'in database_cleaner.rb'
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
# source: http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/
end
Related
I have a feature spec to create an order. That order has a callback like so:
class Order
belongs_to :site
before_validation :add_call_number
def add_call_number
self.call_number = self.site.call_number_start
end
end
In the spec, I get a NoMethod error on call_number_start, because self.site doesn't exist.
I discovered that the Site I created in the Rspec before action doesn't exist at all. In other words...
require 'rails_helper'
describe "create successfully", type: :feature, js: true do
before do
#site = create(:site)
visit "/orders"
.... # various actions to build an order using the page's form
puts ">>>>>"
puts "site in before action: #{Site.all.size}"
find("#checkoutModal #submit").click()
sleep(1)
end
it "should create" do
expect(Order.all.size).to equal(1)
expect(Order.last.call_number).to equal(#site.call_number_start)
end
end
# controller action that #submit POSTs to
def create
puts ">>>>>"
puts "site in controller create: #{Site.all.size}"
puts ">>>"
puts "site_id from order_params: #{order_params[:site_id]}"
#order = Order.new(order_params)
#order.save if #order.valid?
end
def order_params
params.require(:order).permit(:site_id)
end
# puts output:
>>>>>
site in before action: 1
>>>>>
site in controller create: 0
>>>
site_id from order_params: 1
I thought this was a database cleaner issue, so I went really pedantically to manually set it up as truncation method like this:
# rails_helper.rb
Rspec.configure do |config|
config.use_transactional_fixtures = false
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each, truncate: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
How can I correctly test this callback?
You created a site but did not give it to order in the controller.
Is the site coming from order_params in Controller? Then figure out in controller why site is not available.
Is the site an association with the order? Then need to create the site for order:
FactoryBot.create(:site, order: order)
I'm building a toy chat application using Rails 4.2.7, and am writing specs for my controllers using rspec 3.5. My Api::ChatroomsController requires a user to be logged in in order to create a chatroom, so I have created a Api::SessionsHelper module to create sessions from within the Api::ChatroomsController spec.
# app/helpers/api/sessions_helper.rb
module Api::SessionsHelper
def current_user
User.find_by_session_token(session[:session_token])
end
def create_session(user)
session[:session_token] = user.reset_session_token!
end
def destroy_session(user)
current_user.try(:reset_session_token!)
session[:session_token] = nil
end
end
# spec/controllers/api/chatrooms_controller_spec.rb
require 'rails_helper'
include Api::SessionsHelper
RSpec.describe Api::ChatroomsController, type: :controller do
before(:all) do
DatabaseCleaner.clean
User.create!({username: "test_user", password: "asdfasdf"})
end
user = User.find_by_username("test_user")
context "with valid params" do
done = false
# doesn't work if using a before(:all) hook
before(:each) do
until done do
create_session(user)
post :create, chatroom: { name: "chatroom 1" }
done = true
end
end
let(:chatroom) { Chatroom.find_by({name: "chatroom 1"}) }
let(:chatroom_member) { ChatroomMember.find_by({user_id: user.id, chatroom_id: chatroom.id}) }
it "responds with a successful status code" do
expect(response).to have_http_status(200)
end
it "creates a chatroom in the database" do
expect(chatroom).not_to eq(nil)
end
it "adds the chatroom creator to the ChatroomMember table" do
expect(chatroom_member).not_to eq(nil)
end
end
end
I'm using a before(:each) hook with a boolean variable done to achieve the behavior of a before(:all) hook for creating a single session.
If I use a before(:all) hook, I get the error:
NoMethodError: undefined method `session' for nil:NilClass`
I put a debugger in the create_session method of the Api::SessionsHelper module to check self.class and in both cases, when I use before(:each) and when I use before(:all), the class is:
RSpec::ExampleGroups::ApiChatroomsController::WithValidParams
However when using the before(:each) hook, session is {}, while in the before(:all) hook, session gives the NoMethodError above.
Anybody know what causes this error?
You need to include the helper in the test block:
RSpec.describe Api::ChatroomsController, type: :controller do
include Api::SessionsHelper
end
You can also avoid duplication by including common spec helpers in spec/rails_helper.rb
RSpec.configure do |config|
# ...
config.include Api::SessionsHelper, type: :controller
end
This is also where you should put the database_cleaner config. You should use to clean between every spec not just before all as that will lead to test ordering issues and flapping tests.
require 'capybara/rspec'
#...
RSpec.configure do |config|
config.include Api::SessionsHelper, type: :controller
config.use_transactional_fixtures = false
config.before(:suite) do
if config.use_transactional_fixtures?
raise(<<-MSG)
Delete line `config.use_transactional_fixtures = true` from rails_helper.rb
(or set it to false) to prevent uncommitted transactions being used in
JavaScript-dependent specs.
During testing, the app-under-test that the browser driver connects to
uses a different database connection to the database connection used by
the spec. The app's database connection would not be able to access
uncommitted transaction data setup over the spec's database connection.
MSG
end
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, type: :feature) do
# :rack_test driver's Rack app under test shares database connection
# with the specs, so continue to use transaction strategy for speed.
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
if !driver_shares_db_connection_with_specs
# Driver is probably for an external browser with an app
# under test that does *not* share a database connection with the
# specs, so use truncation strategy.
DatabaseCleaner.strategy = :truncation
end
end
config.before(:each) do
DatabaseCleaner.start
end
config.append_after(:each) do
DatabaseCleaner.clean
end
end
I want to be able to always have access to my seed data on my test database.
I understand database_cleaner will remove everything if it's set up that way.
I try to remove everything and then reloading the seed, but when I try to use js: true on a test, the seed never gets loaded so i get errors saying data does not exist.
My spec_helper.rb
RSpec.configure do |config|
# before the entire test suite runs, clear the test database out completely
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
# sets the default database cleaning strategy to be transactions (very fast)
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
# For these types of tests, transactions won’t work. We must use truncation
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
# hook up database_cleaner around the beginning and end of each test, telling it to execute whatever cleanup strategy we selected beforehand.
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
# reload the seed so we have data to play with
end
config.before :all do
Rails.application.load_seed
end
end
When in my view_spec I have something like this
require 'spec_helper'
describe 'my/path', type: :view do
before do
#user = create(:user)
#user.permissions << Permission.first
login_as(#user)
visit my_path
end
it 'should have a valid user, just for kicks' do
#user.should be_valid
end
it 'should be in the path i said' do
expect(current_path).to eq(my_path)
end
describe 'click submit button', js: true do
it 'should take me to a different path' do
click_link('button_1')
expect(current_path).to eq(my_new_path)
end
end
end
The first two test will run and be ok with creating that user, but as soon as it hits that last test with js: true, it no longer has Permission in the database.
Is there a way to tell database_cleaner to only delete the data added by rspec? and not the seed?
Or maybe even tell it to not delete certain tables?
Any help would be appreciated.
Try to use :truncation for all tests with:
DatabaseCleaner.strategy = :truncation
RSpec.configure do |config|
config.before(:each) do
DatabaseCleaner.clean
Rails.application.load_seed
end
end
There also may be an issue with your seeds and not with DatabaseCleaner. You should debug your database state right in the failing test using puts statements or debugger (e.g. pry-byebug).
Currently trying test multiple db's using capybara/rspec/factory girl, though having issues with my DB clearing.
Query error:
': Mysql2::Error: Duplicate entry '1503' for key 'PRIMARY': INSERT INTO `users`
Facilities_spec.rb
feature "User with facilities" do
#current_user = FactoryGirl.create(:user_with_facility)
scenario 'A user can perform a walk-through', :js => true do
login_as
visit '/'
expect(page).to have_text "Our records indicate that you have access to 1 facilities:"
...
end
#current_user = FactoryGirl.create(:user_with_facility)
scenario 'The quick-form requires first_name, last_name, and dob', :js => true do
login_as
visit '/'
expect(page).to have_text "Our records indicate that you have access to 1 facilities:"
...
end
end
rails_helper.rb
cleaner = DatabaseCleaner[:active_record,{:connection => :emp_portal_test}]
rt_cleaner = DatabaseCleaner[:active_record, {connection: :test_rt_treats}]
RSpec.configure do |config|
config.include Capybara::DSL
config.include FactoryGirl::Syntax::Methods
config.use_transactional_fixtures = false
config.before(:suite) do
cleaner.strategy = :truncation
rt_cleaner.strategy = :truncation
end
config.before(:each) do
cleaner.strategy = :truncation
rt_cleaner.strategy = :truncation
cleaner.start
rt_cleaner.start
end
config.before(:each, :js => true) do
cleaner.strategy = :truncation
rt_cleaner.strategy = :truncation
end
config.after(:each) do
cleaner.clean
rt_cleaner.clean
end
user_factory.rb
FactoryGirl.define do
factory :user do
id 33065
first_name "Andrew"
last_name "Larson"
select_id "al44096"
factory :user_with_facility do
after(:create) do |user|
user.facility_assignments << create(:facility)
end
end
end
factory :facility do
id 1550
ref_select_id 1550
status -1
name "St. Paul's Home & Apartments"
name_internal "St Paul's Home"
dept_id "R51"
...
end
When I create a new patient within this test environment, it is wiped before my next use, though I cannot use my same current_user throughout my code.
Ok so a few pieces...
One of my databases' tables did not have an auto-increment feature for it's id which was an issue, so I added auto increment to the table via ...
ALTER TABLE document MODIFY COLUMN document_id INT auto_increment
Once this was resolved my database cleaner was working correctly.
Bonus: I didn't know, but to access #current_user amongst both scenarios I needed
background do
#current_user = FactoryGirl.create(:user_with_facility)
end
I have not seen this until now, and it's great.
2 examples, 0 failures
I have a controller sending in a list of vendors to my controller, and on normal view it's working fine.
class VendorsController < ApplicationController
respond_to :html, :json
def index
#vendor_categories = VendorCategory.where(:is_top_level => true)
#vendors = Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC')
respond_with #vendors
end
end
In my view I have the following two lines:
= debug #vendors
= debug current_user.user_vendor_choices
which, again, are working if I view it in the browser. However, if I test it with Capybara and RSpec, it's empty.
require 'spec_helper'
describe 'Vendors' do
before do
category = create(:vendor_category)
5.times do
vendor = create(:vendor)
vendor_categorization = create(:vendor_categorization, vendor: vendor, vendor_category: category)
p vendor
p category
p vendor_categorization
end
visit signup_path
#new_user = sign_up
end
before(:each) do
visit destroy_user_session_path
visit new_user_session_path
sign_in #new_user
visit vendors_path
end
it 'should save selected vendors', js: true do
p Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC').count
end
end
Vendor.all and the above Vendor.includes... both return values, but for some reason in my test it's not showing anything... getting a Capybara::Element not found.
UPDATE
For testing purposes, I created the Vendors directly with the controller:
def index
#vendor_categories = VendorCategory.where(:is_top_level => true)
4.times do
Vendor.create({name: 'Test McTesterson', vendor_tier_id: 1})
end
#vendors = Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC')
respond_with #vendors
end
Spec passes. What the--? This must be a FactoryGirl issue, or for some reason my records are deleted before it can run the test? Consoling the objects after I create them is showing a record with an ID, which I guess doesn't prove that it's putting them in the database...
Turns out my Database Cleaner activities defined in my spec_helper were a little too vigorous. I had:
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
I had to get rid of the second chunk, so it now reads:
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end
And it works! Not really sure why... any ideas (aside from the obvious, before it was calling database cleaner before/after each test)?
Hi I cursorily glanced at this question, not sure you even need the help anymore, but I think the reason this is failing is a fundamental set up issue that your answer is just patching around.
When you're running a js: true spec (by the way, js: true should be on the describe line, not the it line), short version, Capybara works in different threads, so instance variables created in a before block, unlike with regular Rspec testing, are not available in the spec. To make them available, you have to use a truncation cleaning strategy.
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
TL;DR when running a js test, truncation is basically required (unless obviously you're running js tests that don't require any database interactions). When running all other tests, use transactions (because it's also much faster). I guess your answer replicated this to some extent =)