How to test CSV file download in Capybara and RSpec? - ruby-on-rails

The following is in the controller:
respond_to do |format|
format.csv { send_data as_csv, type:'text/csv' }
end
In spec:
click_link 'Download CSV'
page.driver.browser.switch_to.alert.accept
expect( page ).to have_content csv_data
But this doesn't work:
Failure/Error: page.driver.browser.switch_to.alert.accept
Selenium::WebDriver::Error::NoAlertPresentError: No alert is present
I see the Save File dialog box display, but apparently it is not an "alert" dialog.
How to click OK and get Capybara to see the data?

Adapted from CollectiveIdea and another source.
Works on OSX. Firefox 34.0.5
Spec:
describe 'Download CSV' do
let( :submission_email ){ 'foo#example.com' }
let( :email_csv ){ "id,email,created_at\n1,#{ submission_email }," }
specify do
visit '/emails'
expect( page ).to have_content 'Email Submissions'
click_on 'Download CSV'
expect( DownloadHelpers::download_content ).to include email_csv
end
end
Spec helper:
require 'shared/download_helper'
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
# Suppress "open with" dialog
profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
config.before( :each ) do
DownloadHelpers::clear_downloads
end
shared/download_helper.rb:
module DownloadHelpers
TIMEOUT = 1
PATH = Rails.root.join("tmp/downloads")
extend self
def downloads
Dir[PATH.join("*")]
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.part$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end

I found another way to do this, if you're using the rack_test driver (no javascript/no browser):
DOWNLOAD_CACHE_PATH = Rails.root.join("tmp/downloaded_file").to_s
setup do
File.delete(DOWNLOAD_CACHE_PATH)
end
test "download file" do
visit download_file_path
# simulate file download
File.write(DOWNLOAD_CACHE_PATH, page.body)
csv = CSV.open(DOWNLOAD_CACHE_PATH)
# assert something on the csv data
end

I have tried to implement something similar and spend lots of hours. Finally I have some solution, maybe it fit for you as well.
Gemfile:
#source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bcrypt', '3.1.7'
gem 'bootstrap-sass', '3.2.0.0'
gem 'faker', '1.4.2'
gem 'carrierwave', '0.10.0'
gem 'mini_magick', '3.8.0'
gem 'fog', '1.36.0'
gem 'will_paginate', '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'
gem 'sass-rails', '5.0.2'
gem 'uglifier', '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks', '2.3.0'
gem 'jbuilder', '2.2.3'
gem 'sdoc', '0.4.0', group: :doc
gem 'rename'
gem 'sprockets', '3.6.3'
gem 'responders', '~> 2.0'
gem 'inherited_resources'
group :development, :test do
gem 'sqlite3', '1.3.9'
gem 'byebug', '3.4.0'
gem 'web-console', '2.0.0.beta3'
gem 'spring', '1.1.3'
end
group :test do
gem 'minitest-reporters', '1.0.5'
gem 'mini_backtrace', '0.1.3'
gem 'guard-minitest', '2.3.1'
gem 'capybara', '2.8.1'
gem 'rspec', '3.5.0'
gem 'rspec-rails', '~> 3.4'
gem 'cucumber-rails', :require => false
gem 'shoulda-matchers', '~> 3.0', require: false
gem 'database_cleaner'
gem 'factory_girl_rails', '~> 4.5.0'
end
spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'shoulda/matchers'
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
config.use_transactional_fixtures = false
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'download_helper'
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.include Capybara::DSL
=begin
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
=end
end
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include ApplicationHelper
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email:user.email, password: password, remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
class ActionDispatch::IntegrationTest
# Make the Capybara DSL available in all integration tests
include Capybara::DSL
# Reset sessions and driver between tests
# Use super wherever this method is redefined in your individual test classes
def teardown
Capybara.reset_sessions!
Capybara.use_default_driver
end
end
spec/download_helper.rb
module DownloadHelpers
TIMEOUT = 1
PATH = Rails.root.join("tmp/downloads")
extend self
def downloads
Dir[PATH.join("*")]
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.part$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end
spec/mpodels/spec.rb
describe 'Download file' do
specify do
visit '/createfile'
click_on 'create file'
page.response_headers['Content-Type'].should == "text/csv"
header = page.response_headers['Content-Disposition']
header.should match /^attachment/
header.should match /filename=\"temp.csv\"$/
end
end

Depending on the browser you'll need a profile for that browser, and I'd suggest disabling the property which asks for save in that profile.

#b-seven thanks a lot!
I made my own version of download_helper.rb maybe it will be useful for someone.
spec/support/helpers/download_helpers.rb
module DownloadHelpers
DOWNLOAD_DIR = 'tmp/capybara_downloads'
DOWNLOAD_PATH = ::Rails.root.join(DOWNLOAD_DIR)
def clear_downloads
FileUtils.rm_f(downloads)
end
def downloads
Dir[DOWNLOAD_PATH.join('*')]
end
def download_file(filename)
wait_for_download(filename)
File.read(::Rails.root.join(DOWNLOAD_DIR, filename))
end
def wait_for_download(filename)
Timeout.timeout(Capybara.default_max_wait_time,
Timeout::Error,
"File download timeout! File: #{filename}") do
sleep 0.1 until File.exist?(::Rails.root.join(DOWNLOAD_DIR, filename))
end
end
end

Related

Execution expired on rails feature spec testing a download using capybara/selenium/firefox

Update: After burning too many hours, decided to opt for the easy way out and use rack-test. This works straight out of the box and at least verifies that the content-type is a pdf.
scenario 'document can be downloaded' do
visit my_documents_path
click_on 'Download'
expect(page.response_headers['Content-Type']).to eq "application/pdf"
end
I am trying to write a feature spec to test the contents of my downloaded pdf, and I have followed the directions found here: https://stackoverflow.com/a/29544674/2464546
I am running Ruby 2.3, Rails 4.2.5.2, RSpec 3.4, Capybara 2.7.
In the code from the SO link above, there's a line that supposedly suppresses the Firefox Save popup, with this, adjusted to pdf from csv:
# Suppress "open with" dialog
profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf'
This doesn't seem to work because the dialog box still pops up, and then my test errors out.
My feature spec:
scenario 'document can be downloaded', js: true do
visit my_documents_path
click_on 'Download'
expect(DownloadHelpers::download_content).to have_content('Thingy')
end
Every time I run this spec, it errors out saying execution expired with a different error each time. For example the latest one had the following:
1) My documents home page document can be downloaded
Failure/Error: Dir[PATH.join("*")]
Timeout::Error:
execution expired
# ./spec/features/shared/download_helper.rb:8:in `downloads'
# ./spec/features/shared/download_helper.rb:31:in `downloading?'
# ./spec/features/shared/download_helper.rb:27:in `downloaded?'
# ./spec/features/shared/download_helper.rb:22:in `block in wait_for_download'
# ./spec/features/shared/download_helper.rb:21:in `wait_for_download'
# ./spec/features/shared/download_helper.rb:16:in `download_content'
# ./spec/features/my_documents/index_spec.rb:42:in `block (2 levels) in <top (required)>'
In the DownloadHelpers module, I've changed the sleep from 0.1 to 1 to 3, and occasionally I'll get the following error, with the sleep count changing w/ whatever I've set it to:
Failure/Error: sleep 3 until downloaded?
Timeout::Error:
execution expired
# ./spec/features/shared/download_helper.rb:22:in `sleep'
# ./spec/features/shared/download_helper.rb:22:in `block in wait_for_download'
# ./spec/features/shared/download_helper.rb:21:in `wait_for_download'
# ./spec/features/shared/download_helper.rb:16:in `download_content'
# ./spec/features/my_documents/index_spec.rb:42:in `block (2 levels) in <top (required)>'
I have also changed the TIMEOUT count with no change in failure result from the above. Ultimately, the dialog box still pops up and does not go away/doesn't look like it downloads the file.
My controller behind the Download button:
def download
pdf = #document.pdf_file_name
send_file pdf
end
The created document is not large, as all it has is a name and a few lines, so I don't suspect it'd need more than a few seconds to download and read the file.
Why is the execution expiring? How do I get Capybara/Feature spec to download the file so my expectation passes?
The view document.haml using angular tho I don't think that matters.
%li.col-xs-6
%li.col-xs-6= link_to 'Download', download_document_path(id: document.id), "ng-click" => "logAnalytics('#{document.document_template_id}', 'download')"
Also, my feature spec has require 'rails_helper' at the top, and my rails_helper has the code from the SO post (in full, w/ relevant bits):
require 'features/shared/download_helper'
RSpec.configure do |config|
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf'
Capybara::Selenium::Driver.new(app, browser: :firefox, profile: profile)
end
config.before( :each ) do
DownloadHelpers::clear_downloads
end
end
I think any of the following will work:
page.driver.browser.switch_to.alert.accept
# or
page.driver.browser.switch_to.alert.dismiss
# or
page.driver.browser.switch_to.alert.text
Check the mime type your pdf download is returning, odds are it's not actually 'application/pdf' and is most likely being returned as 'application/octet-stream'. You should fix the return type to be correct, but in the short term you can try changing to profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf,application/octet-stream'
I got the 'execution expired' error too. I followed all the steps above, but the error remained. It turned out to be the download directory was missing in the filesystem (DownloadHelpers::PATH).
Creating that one made the message disappear and the test pass.
I was experiencing this same error. The solution for me was to increase the timeout. So where ever you are doing the sleeping until timeout, increase the timeout. Of course it depends on your business logic but 5 seconds works for me consistently whereas 2 seconds failed consistently.
I have tried to implement something similar and spend lots of hours. Finally I have some solution, maybe it fit for you as well.
Gemfile:
#source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bcrypt', '3.1.7'
gem 'bootstrap-sass', '3.2.0.0'
gem 'faker', '1.4.2'
gem 'carrierwave', '0.10.0'
gem 'mini_magick', '3.8.0'
gem 'fog', '1.36.0'
gem 'will_paginate', '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'
gem 'sass-rails', '5.0.2'
gem 'uglifier', '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks', '2.3.0'
gem 'jbuilder', '2.2.3'
gem 'sdoc', '0.4.0', group: :doc
gem 'rename'
gem 'sprockets', '3.6.3'
gem 'responders', '~> 2.0'
gem 'inherited_resources'
group :development, :test do
gem 'sqlite3', '1.3.9'
gem 'byebug', '3.4.0'
gem 'web-console', '2.0.0.beta3'
gem 'spring', '1.1.3'
end
group :test do
gem 'minitest-reporters', '1.0.5'
gem 'mini_backtrace', '0.1.3'
gem 'guard-minitest', '2.3.1'
gem 'capybara', '2.8.1'
gem 'rspec', '3.5.0'
gem 'rspec-rails', '~> 3.4'
gem 'cucumber-rails', :require => false
gem 'shoulda-matchers', '~> 3.0', require: false
gem 'database_cleaner'
gem 'factory_girl_rails', '~> 4.5.0'
end
spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'shoulda/matchers'
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
config.use_transactional_fixtures = false
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'download_helper'
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.include Capybara::DSL
=begin
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
=end
end
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include ApplicationHelper
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email:user.email, password: password, remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
class ActionDispatch::IntegrationTest
# Make the Capybara DSL available in all integration tests
include Capybara::DSL
# Reset sessions and driver between tests
# Use super wherever this method is redefined in your individual test classes
def teardown
Capybara.reset_sessions!
Capybara.use_default_driver
end
end
spec/download_helper.rb
module DownloadHelpers
TIMEOUT = 1
PATH = Rails.root.join("tmp/downloads")
extend self
def downloads
Dir[PATH.join("*")]
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.part$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end
spec/mpodels/spec.rb
describe 'Download file' do
specify do
visit '/createfile'
click_on 'create file'
page.response_headers['Content-Type'].should == "text/csv"
header = page.response_headers['Content-Disposition']
header.should match /^attachment/
header.should match /filename=\"temp.csv\"$/
end
end

How to test file downloads in Capybara using Selenium? [duplicate]

The following is in the controller:
respond_to do |format|
format.csv { send_data as_csv, type:'text/csv' }
end
In spec:
click_link 'Download CSV'
page.driver.browser.switch_to.alert.accept
expect( page ).to have_content csv_data
But this doesn't work:
Failure/Error: page.driver.browser.switch_to.alert.accept
Selenium::WebDriver::Error::NoAlertPresentError: No alert is present
I see the Save File dialog box display, but apparently it is not an "alert" dialog.
How to click OK and get Capybara to see the data?
Adapted from CollectiveIdea and another source.
Works on OSX. Firefox 34.0.5
Spec:
describe 'Download CSV' do
let( :submission_email ){ 'foo#example.com' }
let( :email_csv ){ "id,email,created_at\n1,#{ submission_email }," }
specify do
visit '/emails'
expect( page ).to have_content 'Email Submissions'
click_on 'Download CSV'
expect( DownloadHelpers::download_content ).to include email_csv
end
end
Spec helper:
require 'shared/download_helper'
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
# Suppress "open with" dialog
profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
config.before( :each ) do
DownloadHelpers::clear_downloads
end
shared/download_helper.rb:
module DownloadHelpers
TIMEOUT = 1
PATH = Rails.root.join("tmp/downloads")
extend self
def downloads
Dir[PATH.join("*")]
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.part$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end
I found another way to do this, if you're using the rack_test driver (no javascript/no browser):
DOWNLOAD_CACHE_PATH = Rails.root.join("tmp/downloaded_file").to_s
setup do
File.delete(DOWNLOAD_CACHE_PATH)
end
test "download file" do
visit download_file_path
# simulate file download
File.write(DOWNLOAD_CACHE_PATH, page.body)
csv = CSV.open(DOWNLOAD_CACHE_PATH)
# assert something on the csv data
end
I have tried to implement something similar and spend lots of hours. Finally I have some solution, maybe it fit for you as well.
Gemfile:
#source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bcrypt', '3.1.7'
gem 'bootstrap-sass', '3.2.0.0'
gem 'faker', '1.4.2'
gem 'carrierwave', '0.10.0'
gem 'mini_magick', '3.8.0'
gem 'fog', '1.36.0'
gem 'will_paginate', '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'
gem 'sass-rails', '5.0.2'
gem 'uglifier', '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks', '2.3.0'
gem 'jbuilder', '2.2.3'
gem 'sdoc', '0.4.0', group: :doc
gem 'rename'
gem 'sprockets', '3.6.3'
gem 'responders', '~> 2.0'
gem 'inherited_resources'
group :development, :test do
gem 'sqlite3', '1.3.9'
gem 'byebug', '3.4.0'
gem 'web-console', '2.0.0.beta3'
gem 'spring', '1.1.3'
end
group :test do
gem 'minitest-reporters', '1.0.5'
gem 'mini_backtrace', '0.1.3'
gem 'guard-minitest', '2.3.1'
gem 'capybara', '2.8.1'
gem 'rspec', '3.5.0'
gem 'rspec-rails', '~> 3.4'
gem 'cucumber-rails', :require => false
gem 'shoulda-matchers', '~> 3.0', require: false
gem 'database_cleaner'
gem 'factory_girl_rails', '~> 4.5.0'
end
spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'shoulda/matchers'
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
config.use_transactional_fixtures = false
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'download_helper'
Capybara.register_driver :selenium do |app|
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = DownloadHelpers::PATH.to_s
profile['browser.download.folderList'] = 2
profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.include Capybara::DSL
=begin
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
=end
end
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include ApplicationHelper
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email:user.email, password: password, remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
class ActionDispatch::IntegrationTest
# Make the Capybara DSL available in all integration tests
include Capybara::DSL
# Reset sessions and driver between tests
# Use super wherever this method is redefined in your individual test classes
def teardown
Capybara.reset_sessions!
Capybara.use_default_driver
end
end
spec/download_helper.rb
module DownloadHelpers
TIMEOUT = 1
PATH = Rails.root.join("tmp/downloads")
extend self
def downloads
Dir[PATH.join("*")]
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.part$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end
spec/mpodels/spec.rb
describe 'Download file' do
specify do
visit '/createfile'
click_on 'create file'
page.response_headers['Content-Type'].should == "text/csv"
header = page.response_headers['Content-Disposition']
header.should match /^attachment/
header.should match /filename=\"temp.csv\"$/
end
end
Depending on the browser you'll need a profile for that browser, and I'd suggest disabling the property which asks for save in that profile.
#b-seven thanks a lot!
I made my own version of download_helper.rb maybe it will be useful for someone.
spec/support/helpers/download_helpers.rb
module DownloadHelpers
DOWNLOAD_DIR = 'tmp/capybara_downloads'
DOWNLOAD_PATH = ::Rails.root.join(DOWNLOAD_DIR)
def clear_downloads
FileUtils.rm_f(downloads)
end
def downloads
Dir[DOWNLOAD_PATH.join('*')]
end
def download_file(filename)
wait_for_download(filename)
File.read(::Rails.root.join(DOWNLOAD_DIR, filename))
end
def wait_for_download(filename)
Timeout.timeout(Capybara.default_max_wait_time,
Timeout::Error,
"File download timeout! File: #{filename}") do
sleep 0.1 until File.exist?(::Rails.root.join(DOWNLOAD_DIR, filename))
end
end
end

Factory Not Registered in rspec but found in console

I have the followed
group :test, :development do
gem 'rspec-rails', '~> 3.0'
gem "guard-rspec"
gem 'capybara'
gem 'factory_girl_rails'
gem 'turn'
gem 'guard' # NOTE: this is necessary in newer versions
gem 'poltergeist'
gem 'phantomjs', :require => 'phantomjs/poltergeist'
gem 'selenium-webdriver'
gem 'capybara-screenshot'
gem 'capybara-webkit'
gem 'zeus'
gem "parallel_tests"
gem 'launchy'
gem 'email_spec'
gem 'action_mailer_cache_delivery'
gem 'protractor-rails'
gem 'database_cleaner'
and in my spec_helper.rb
require 'capybara/rspec'
require 'factory_girl_rails'
require 'support/request_helpers'
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include RequestHelpers
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
Finally I have such a spec helper in the spec/support/request_helper.rb
module RequestHelpers
def create_logged_in_user
user = create(:user)
login(user)
user
end
def login(user)
login_as user, scope: :user
end
end
and factories.rb
# /spec/factories/factories.rb
FactoryGirl.define do
factory :user do
first "John"
last 'Smith'
email 'test#example.com'
password 'johnsmith123'
end
end
Yes everytime I run rpsec it shows that
Factory not registered: user
And when I rune FactoryGirl.factories in the rails console, i can see 'user' is registered
=> #<FactoryGirl::Registry:0x007fa4e0e18160
#items=
{
:user=>
#<FactoryGirl::Factory:0x007fa4e2371200
#aliases=[],
#class_name=nil,
#compiled=false,
#definition=
#<FactoryGirl::Definition:0x007fa4e2370f58
#additional_traits=[],
#attributes=nil,
#base_traits=[],
#callbacks=[],
#compiled=false,
#constructor=nil,
#declarations=
#<FactoryGirl::DeclarationList:0x007fa4e2370ee0
#declarations=
[#<FactoryGirl::Declaration::Static:0x007fa4e2370b48 #ignored=false, #name=:first, #value="john">,
#<FactoryGirl::Declaration::Static:0x007fa4e2370a80 #ignored=false, #name=:last, #value="smith">,
#<FactoryGirl::Declaration::Static:0x007fa4e23709b8 #ignored=false, #name=:email, #value="test#example.com">,
I read through the Github setup docs for factory_girl_rails, rspec-rails multiple times but found no solution.
Does anyone know where I should look to detect the problem?
Thanks!
EDITED added contents of spec/rails_helper.rb updated Wed, 10 Jun 2015
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
require 'capybara/poltergeist'
require 'capybara-screenshot/rspec'
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, :phantomjs => Phantomjs.path, :inspector => true)
end
Capybara.javascript_driver = :webkit
include Warden::Test::Helpers
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
I had the same issue
Why is FactoryBot not finding our factories only inside RSpec, I do not know.
But making FactoryBot explicitly find my definitions worked.
So you just need to add this to your spec_helper:
FactoryBot.find_definitions
in my case, I followed the general instructions in this answer:
https://stackoverflow.com/a/49436531/8790125
So I create a file spec/support/factory_bot.rb
with this code (note the find_definitions):
require 'factory_bot'
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
FactoryBot.find_definitions
end
and then required on rails_helper:
# ...
require 'rspec/rails'
require 'support/factory_bot'
# ...

Factory Girl uninitialized constant Model Name with rails-api

I'm trying to make a factory for my User model, but when I run rspec, I get
Failure/Error: user = build(:user, email: "invalid#email.com")
NameError:
uninitialized constant User
When I hop into rails console, I can do User.all and it will return the users I have and FactoryGirl.build(:user, email: "invalid#email.com") works just fine, so I'm not sure where the problem is.
I've created the project with rails-api, using rails 4.2 ruby 2.2
# spec/spec_helper.rb
# i have to set spec/factories as the location explicitly or it won't find the factories
require 'factory_girl_rails'
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
FactoryGirl.definition_file_paths = [File.expand_path('../factories', __FILE__)]
FactoryGirl.find_definitions
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.filter_run :focus
config.run_all_when_everything_filtered = true
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
end
# spec/factories/user.rb
FactoryGirl.define do
factory :user do
email "test#example.com"
password "password"
end
end
#spec/requests/api/v1/sign_in_spec.rb
require 'spec_helper'
describe "Signing In" do
it "will reject an invalid user" do
user = build(:user, email: "invalid#email.com")
post :new_api_v1_user_session, user: { email: user.email, password: user.password}
is_expected.to respond_with 401
end
end
#Gemfile
ruby '2.2.0'
gem 'rails', '4.2.0'
gem 'rails-api'
gem 'spring', :group => :development
gem 'pg'
gem 'active_model_serializers', '0.8.3'
gem 'devise'
gem 'simple_token_authentication'
gem 'rdoc'
gem 'responders'
group :development, :test do
gem 'rspec-rails'
gem 'factory_girl_rails'
end
Thanks!
Update: I can run FactoryGirl.build(:user) with rails console test without any issues...
Looks like all I needed to do was require 'rails_helper' in my spec_helper.rb. This also allowed me to remove the FactoryGirl.definition_file_paths from spec_helper.

FactoryGirl + Rspec + Rails3 failing

I have this project from my university and I'm trying to understand the code before making any changes.
The code has tests, which make this easier. But I'm having problems in run these tests. The person who wrote the code said it was working perfectly, so I think it's some kind of incompatibility with gems or something like that. The project has more than one year without updates, that's why the gems may be a little old rs. But since I haven't change any gems, this should work. Lets go to the problem:
Here is my test:
it 'edit user information' do
user = FactoryGirl.create :user
login(user.email, '123456')
click_link user.email
fill_in 'E-mail', :with => 'another_email#email.com'
click_button 'Save'
page.should have_content 'Update successfull.'
end
and here is my login function, that is located at spec_helper
def login(email, password)
visit '/admin'
fill_in('user_email', :with => email)
fill_in('user_password', :with => password)
click_button "Entrar"
end
And that is the error:
1) login and register edit user information
Failure/Error: login(user.email, '123456')
ArgumentError:
wrong number of arguments (1 for 0)
# ./spec/acceptance/login_spec.rb:18:in `block (2 levels) in <top (required)>'
where line 18 is: login(user.email, '123456')
After a while I realized that:
If I put a string as a parameter to login function, like login('login#email.com', '123456') instead of login(user.email, '123456'). It runs, but I get another error because login#email doesn't exist on my database then I can't log in.
But, if this string is exactly the same as FactoryGirl generates, I get the error of arguments again.
I've already tried update almost all gemfiles, but it was not successful. So, I'm still working with the old gems until I solve this problem. Here is my gemfile:
source 'http://rubygems.org'
gem 'rails', '3.2.11'
gem 'rake', '10.1.0'
gem 'mysql2', '0.3.11'
gem 'devise', '2.2.3'
gem 'fastercsv', '1.5.5'
gem 'rails_admin', '0.4.3'
gem 'cancan', '1.6.9'
gem 'simple_form', '2.0.4'
gem 'slim', '1.3.6'
gem 'jquery-rails', '2.2.0'
gem 'sprockets', '~> 2.2.1'
gem 'ckeditor', '4.0.2'
gem 'paperclip', '3.4.0'
gem 'twitter-bootstrap-rails', '2.2.0'
gem 'bootstrap-wysihtml5-rails', '0.3.1.17'
gem 'less-rails', '2.2.6'
gem 'therubyracer', '~> 0.12.0'
gem 'kaminari', '0.14.1'
group :assets do
gem 'uglifier', '1.3.0'
gem 'yui-compressor', '0.9.6'
end
group :test do
gem 'rspec-rails', '2.11.0'
gem 'capybara', '~> 2.0.2'
gem 'launchy', '2.1.2'
gem 'factory_girl_rails', '1.6.0'
gem 'valid_attribute', '1.3.1'
gem 'spork', '0.9.2'
gem 'capybara-webkit', '1.1.1'
gem 'database_cleaner', '0.8.0'
gem 'shoulda-matchers'
gem 'autotest'
end
group :developmet do
gem 'rails3-generators'
gem 'slim-rails', '1.1.0'
gem 'thin', '1.5.0'
end
Can anyone help me with this issue?
Edit:
Factory user:
# -*- encoding : utf-8 -*-
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password "123456"
password_confirmation "123456"
admin true
end
end
spec_helper.rb:
# -*- encoding : utf-8 -*-
require 'rubygems'
require 'spork'
Spork.prefork do
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'valid_attribute'
require 'cancan/matchers'
require 'paperclip/matchers'
IMAGE = File.expand_path("../data/image.jpg", __FILE__)
TEXT = File.expand_path("../data/text.txt", __FILE__)
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
Capybara.javascript_driver = :webkit
RSpec.configure do |config|
config.mock_with :rspec
config.use_transactional_fixtures = false
config.include Paperclip::Shoulda::Matchers
config.before :each do
if example.metadata[:js]
Capybara.server_port = 33333
Capybara.current_driver = :webkit
end
if Capybara.current_driver == :rack_test
DatabaseCleaner.strategy = :transaction
else
DatabaseCleaner.strategy = :truncation
end
DatabaseCleaner.start
end
config.after do
DatabaseCleaner.clean
Capybara.use_default_driver if example.metadata[:js]
end
end
def login(email, password)
visit '/admin'
fill_in('user_email', :with => email)
fill_in('user_password', :with => password)
click_button 'Entrar'
end
end
Spork.each_run do
require File.expand_path("../../config/routes", __FILE__)
load "#{Rails.root}/config/routes.rb"
Dir["#{Rails.root}/app/**/*.rb"].each { |f| load f }
end
login_spec.rb:
# -*- encoding : utf-8 -*-
require 'spec_helper'
feature 'login and register' do
background do
FactoryGirl.create :configuration
end
it 'should not be possible guest user register an administrator' do
visit '/admin'
page.should_not have_content 'Registrar'
lambda { visit '/users/sign_up' }.should raise_error ActionController::RoutingError
end
it 'edit user information' do
user = FactoryGirl.create :user
login(user.email, '123456')
click_link user.email
fill_in 'E-mail', :with => 'outro_email#email.com'
click_button 'Salvar'
page.should have_content 'Usuário atualizado(a) com sucesso.'
end
end

Resources