I am writing a scenario for signup form.
#abc
#selenium
Scenario:Non registered user signs up
Given I am on the sign-up page
When I fill in the following:
|first_name|Anidhya|
|last_name|Ahuja|
|email|anidhya#gmail.com|
|password|123456|
And I press "submit"
Then I should see "Registration complete"
I want to use database cleaner to roll back the test database after this scenario so that I can use this scenario again and again.
For that inside my env.rb file I wrote:
begin
require 'database_cleaner'
require 'database_cleaner/cucumber'
DatabaseCleaner.strategy = :transaction
Cucumber::Rails::World.use_transactional_fixtures = true
rescue NameError
raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
end
Before('#abc') do
DatabaseCleaner.start
end
After('#abc') do
DatabaseCleaner.clean
end
Now when I run the scenario , the user gets saved in the database and the database cleaner fails. I dont see any error messages
Could you please clarify how to use database cleaner for only one scenario.I only want to use cleaner for this scenario.
Also could you please also provide the vital difference between using truncation and transaction.I think truncation clears the whole database but I dont want that.
Is there a better method of doing signup testing than this?
You can't run transactions with selenium because the test runs on two separate instances of the app AFAIK
Related
I'm writing a gem for catching flaky tests. The idea is to dump database to locate randomized factory. I'm using system call in the .after hook:
config.after do |example|
system "pg_dump -U postgres -d #{db_name} > #{path}/#{status}.sql"
end
At this moment database contains a record - i've made sure of that by byebug.
However i bump into a problem - as i see, transaction is not closed, so if i try get record via psql - i get nothing, record doesn't exist.
Any ideas how to close transaction during spec? Or maybe i'm wrong somewhere deeply?
Yes, the matter is the transaction was not closed. Database cleaner wrap all rspec execution in begin-rollback. I found two ways to solve my problem:
Use active record connection to dump database content, but it's no so easy - you have to use COPY command to copy (unexpected, huh?) ALL tables to file (.csv for example) and then restore them. Ew, tedious.
Change database cleaner strategie from :transaction to :truncation:
config.before(:each, flaky: true) do
DatabaseCleaner.strategy = :truncation
end
That's the solution.
In Rspec, I'm creating records, e.g. let!(:user) { create(:user) }.
I can access the new user in Rspec and in the main thread of the subject. Both of these return the user:
puts User.all.to_a
puts ActiveRecord::Base.connection.exec_query('select * from users')
However, I can now longer access the user in a new thread:
Thread.new do
puts User.all.to_a
puts ActiveRecord::Base.connection.exec_query('select * from users')
end
How do I fix this? Is this just an Rspec issue? I don't think I can reproduce it in Rails console.
You have probably configured RSpec to run its test within a database transaction
Quote from the RSpec Docs:
>
When you run rails generate rspec:install, the spec/rails_helper.rb file
includes the following configuration:
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
The name of this setting is a bit misleading. What it really means in Rails
is "run every test method within a transaction." In the context of rspec-rails,
it means "run every example within a transaction."
The idea is to start each example with a clean database, create whatever data
is necessary for that example, and then remove that data by simply rolling back
the transaction at the end of the example.
You might want to disable this configuration when your application tests have the requirement to support multiple threads at the same time.
But that means that your test database will not be cleared from test data automatically anymore when a test is done. Instead, you will need to delete the test data manually or use another gem (like database_cleaner) to reset the database after running tests.
I've recently started testing my app using RSpec. Being the testing noob that i am, i did not install the full suite of tools that people normally use (namely FactoryGirl, also now known as FactoryBot. And DatabaseCleaner).
Anyway here's what happened. After getting my feet wet with RSpec, i started using FactoryBot so that my tests looks less convoluted. Note that :user is a Devise object.
# spec/models/first_test_spec.rb
FactoryBot.create(:user)
#Other Code
So i'm doing stuff with my first_test_spec.rb and i run it and everything is working perfectly fine. I then create my 2nd spec file
# spec/models/second_test_spec.rb
FactoryBot.create(:user)
#Other Code
Now here comes the problem. My tests are failing because FactoryBot.create(:user) is invalid, due to Devise requiring unique emails. Clearly this indicates that the data from my first_test_spec is persisting hence the error.
So i attempt to install DatabaseCleaner and hopefully clear my Test DB after each run. My rails_helper looks like this:
require 'spec_helper'
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 'rspec/rails'
require 'support/factory_bot'
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
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
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
I think i've got everything set up correctly, so i'm uncertain if the errors are still occurring due to my DatabaseCleaner being set up wrongly.
Anyway, running rspec still throws the same error, where second_test_spec cannot create the user object because the email already exists (a validation that comes with Devise itself).
I then proceed to run the following:
rake db:test:prepare
rspec spec/models/second_test_spec.rb
And it still throws the same error. So right now, i have no idea if my database is being cleanse after running rspec. But i'm quite certain that i have been unable to purge my test database.
So i guess i really have 2 questions/problems:
1) How do you purge your test database? (Googling reveals that rake db:test:prepare is the way)
2) Is my DatabaseCleaner setup correctly? If so, shouldn't it be purging the database?
UPDATE:
As suggested to me in the comments, using sequence for creating unique fields with FactoryBot is recommended. Obviously that made the problem go away because there would no longer be validation errors from Devise.
I then went on to test a couple of things.
rails c test
I ran this to check my test database and it was indeed empty. This indicates that DatabaseCleaner is working perfectly fine. What i fail to understand then, is why the need to sequence my email creation in FactoryBot? Or i suppose, i fail to understand how does RSpec "compile & run".
puts #user.email
So i wanted to print out the emails to look at the sequencing to see if i'm able to decipher the problem. Here's what happens:
running rspec spec/models/first_test_spec.rb
Tutor email yields the number 2.
running rspec spec/models/second_test_spec.rb
Tutor email yields the number 3.
running rspec
Tutor email yields the numbers 2 & 5.
So i'm not sure if there are "problems" with my test suite. Clearly my original problem has been fixed, and this is a separate topic altogether. But i figured if anyone would like to explain this mystery to anyone else who chances upon this thread may wish to do so.
Seeing your full spec files would help. If you are running the specs as you've written - creating a user outside of an example group or a before block - that will cause records to be written outside of the cleaning strategy scope and can cause data to remain across examples. Your DBcleaner config looks to be set up fine otherwise.
rake db:test:prepare is the correct way to clean out your test db but shouldn't need to be ran often unless you have migration changes. You can jump into a rails console within the test environment rails c test and look around to see if there are any records left behind.
As a side note you can flip config.use_transactional_fixtures from false to true and remove DBcleaner altogether from your app. Just make sure there is no residual data in your database before going this route.
So far I use rspec and factories only, not fixtures or seed data. But now I need a (static) Coupon table with a dozen or so records of config data to be available to my app when running my integration tests (rspec + capybara, not cuke).
My rspec tests have a FactoryGirl factory that creates a Store.
In my app, when a Store is initialized by init_store(coupon_code), a coupon code is passed in that turns on/off various features (by looking up the settings for that coupon in Coupon table).
So, when I create a Store factory, I also need to specify (via trait?) a coupon code. Then, down in my model, the method that initializes a store will use the coupon code to lookup a bunch of settings in the Coupon table. (Each coupon has about 20 attributes that turn on/off various Store features.)
My coupons.yml file contains the actual coupon table data used by my app. Now I need to make them available to the app when tests are being run. (Don't care about 'brittle', this table doesn't change.)
Having not used fixtures OR seed data with rspec, I'm not sure how/where to put my coupons.yml file and how to make the coupons 'load' into the test Coupon table so they are available to my app while tests are running.
Do I pre-populate seed data with coupons.yml? If so, how can I have that get loaded into my test database each time my tests run?
Or, should I use fixtures?
Either way, where should coupons.yml go and how do I load it (in the Store factories? in one of the specs? In specs_helper?)
When I search for stuff like "using fixtures and factories" or "using seed data with rspec" all I find is the advice "use factories" and yeah, I get that. But for this particular situation I need both, so any help would be appreciated.
I do something like this, and I use seeds rather than fixtures (though I'm sure you could use fixtures too).
In spec_helper.rb
RSpec.configure do |config|
...
config.before :suite do
DatabaseCleaner.strategy = :truncation, {:except => %w[table1 table2]}
DatabaseCleaner.clean_with :truncation
Rails.application.load_seed
end
...
end
So the DatabaseCleaner.strategy line is telling database_cleaner to clean everything except the array of tables you pass it.
After setting the strategy, you simply call Rails.application.load_seed to load the seed data.
Hope that helps, let me know if you have any questions.
I have the following code in a spec:
it 'should save some favorite locations' do
user=FactoryGirl.create(:user) # not adding anything
and it doesn't seem to write anything to the database. Is FactoryGirl intended to write something with this in a model spec? If I run from the rails console, it does add to the database. Why isn't a test in an rspec isn't running this? Is that how it is intended to work?
thx
If you have configured rspec to use database transactions for each test, or to use database truncation, then any records created are rolled back or destroyed.
To check that it really is adding something, you could try:
it 'should save some favorite locations' do
user=FactoryGirl.create(:user) # not adding anything
User.find(user.id).should_not be_nil # ensure it is in database
If it passes, then it was added to the database.
If you are using database transactions for your tests, then the database is rolled back after each test. If you need to use a record which is created in multiple tests, you can use:
before(:all) do
#user=FactoryGirl.create(:user)
end
after(:all) do
#user.destroy # with create in before(:all), it is not in a transaction
# meaning it is not rolled back - destroy explicitly to clean up.
end