I need to seed some geographical data for my test, and I'm not sure that I'm taking the right approach here, but here is how I've tried.
In my spec helper:
config.before(:each, location_data: true) do |example|
address = FactoryGirl.create(:address, atitude: 20.9223, longitude: -72.551)
end
A specific address point I created. Then I have these, which I think are ruining my data :
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Then in my test I have this:
context 'nearby', location_data: true do
context 'there should be 0 matches in radius' do
binding.pry
#When I debug here there are 0 addresses created
expect(Address.count).to eq(1)
end
end
When I look at the test log its like my test data setup is not even executed, what am I doing wrong here? I need this address for various tests, not just one but many complex scenarios and I would reuse the addresses in another tests, that's why I put them in a rspec config to make it more DRY
Changes as suggested in max answer :
module LocationData
extend ActiveSupport::Concern
included do
let!(:address) { FactoryGirl.create(:address, latitude: 20.9223, longitude: -72.551) }
end
end
Then in my test:
require 'support/location_data'
describe MyModel do
include LocationData
context 'nearby' do
context 'there should be 1 matches in radius' do
binding.pry
#When I debug here there are 0 addresses created
expect(Address.count).to eq(1)
end
end
end
Still get 0 address count when I count addresses. Not sure what am I doing wrong.
SOLUTION (thanks max):
I was missing it block in context block :
context 'there should be 1 matches in radius' do
binding.pry
#When I debug here there are 0 addresses created
it 'has one address before' do
expect(Address.count).to eq(1)
end
end
A good test suite will empty the database between each example.
Why? Stuffing a bunch of data into your database and then running some test on the same DB data sounds like a good idea at first. But if you tests alter that data than you soon end up with ordering issues which can cause flapping tests and serious headaches. Its an approach that has been tested and found lacking.
Instead you want a clean slate for each test. DatabaseCleaner does just that. It's not ruining your data - it's keeping your data from ruining your test suite and or sanity.
You never want to create test data in your rspec configuration. Use it to setup the tools you need to run your test. If you start creating a bunch of flags to set up data from your config it's going to get out of control quickly. And you don't really need the exact same data as often as you think.
Instead if you find yourself repeatedly setting up the same data in your specs you can dry it out with example groups. Or create named factories with FactoryGirl.
module GeocodedExampleGroup
extend ActiveSupport::Concern
included do
let(:address) { FactoryGirl.create(:address, latitude: 20.9223, longitude: -72.551) }
end
end
require 'rails_helper'
require 'support/example_groups/geocoded'
describe SomeModel do
include GeocodedExampleGroup
# ...
end
Related
I'm writing rspec tests like so
describe Module do
describe "method" do
context "first_context" do
before do
stub_const("Module::CONST", "stub0")
end
# logic
end
context "second_context" do
before do
stub_const("Module::CONST", "stub0 stub1")
end
# logic
end
end
end
and about 75% of the time the tests pass as the stub_const logic is working, but 25% of the time a race condition fails and the stub_const from the first test flows in to the const for the second test, so the second test's Module::CONST value is "stub0". Why is this happening?
I've seen this sort of thing happen on JRuby. You can try adding explicit locking around any code that stubs globals, or running each of the examples under a lock:
$lock = Mutex.new
around do |example|
$lock.synchronize do
example.run
end
end
Ensure this is before your before hooks.
I am trying to test a dropdown with capybara. It works fine if I don't clean the database. I have to clean the database for other test to function properly.
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, {:except => %w[questions]}
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
If I comment out the database cleaner the test must run atleast twice to pass. I can see it retry and always pass on the second try with the same criteria. Once the database cleaner is put into place the test paramenters change on each iteration so it doesn't get a chance to test it a second time.
context 'when there are questions and answers in the system' do
before do
allow_any_instance_of(Form).to receive(:create_questions)
question = create(:question)
people.map do |person|
create(:answer, question: question, person: person) if [true, false].sample
end
end
it 'should allow filtering by whether a person has answered a specific question', js: true do
#question = Question.first
select_from_dropdown #question.text, from: 'question'
click_on 'Search'
people.each do |person|
if person.questions.include? #question
expect(page).to have_content person.full_name
else
expect(page).to_not have_content person.full_name
end
end
end
end
This is the helper method which explores the drop down for capybara
def select_from_dropdown(item_text, options)
# find dropdown selector
dropdown = find_field(options[:from], visible: false).first(:xpath, './/..')
# click on dropdown
dropdown.trigger('click')
# click on menu item
dropdown.find('.menu .item', text: item_text, visible: false).trigger('click')
end
I have tried sleep throughout the select_from_dropdown thinking that maybe it wasn't loading fast enough but that is not the problem. Ideally this test should work on the first try, but at the very least it needs to pass with the database cleaner.
There is no visit shown in the test, which means you've visited the page before the questions are created and therefore the info expected is not shown on the page. The data would exist from the first run on the second run if you don't clear the DB which explains why it passes second time around. You should always visit the page after creating the test data.
Additionally as I mentioned in the comments you probably want to fix (remove if using Rails 5.1+) your DatabaseCleaner config and the use of trigger is a really bad idea in tests since it can do things a user never could.
I have gem devise and gem apartment which I'm using to create separate schemas for each devise's user account.
Apartment's doc and advice in that issue suggest to use Rack middleware to switch between tenants. In that case it's not possible (as far as I know) as I have it user depended rather than request depended.
All works just great except my RSpec tests. The problem is that after every test database is not clean properly (it doesn't remove schema for new created user). All tests pass if I run a small set of them but if I run to many than Faker::Internet.first_name generates usernames that already was taken (which is not valid).
So this is how I did it:
app/controllers/application_controller.rb
def scope_tenant
Apartment::Database.switch(current_user.username)
end
app/controllers/albums_controller.rb (album model belong_to :user)
class AlbumsController < ApplicationController
before_action :authenticate_user! # devise magic
before_action :scope_tenant
app/model/user.rb
after_create :create_schema
private
def create_schema
Apartment::Database.create(self.username)
end
This is what I've added to my specs:
spec/factories/user.rb
FactoryGirl.define do
factory :user do
username { Faker::Name.first_name }
email { Faker::Internet.email("#{username}") }
password "login_as will not use it anyway"
end
end
spec/support/auth_helpers.rb
Warden.test_mode!
def login_and_switch_schema(user)
login_as(user)
Apartment::Database.switch(user.username) # for some reason `login_as()` didn't do that by itself
end
spec/features/albums_spec.rb
feature "Album Pages" do
given(:user) { create(:user) }
given(:album) { create(:album) }
around :each do
login_and_switch_schema user
end
scenario...
As I have some tests with js: true than I have that:
spec/support/database_cleaner.rb
RSpec.configure do |config|
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
end
Current commit for all sources are available at my github here.
So.. the main question is: how to clean database created schemas for each user after test ? I'll appreciate any other comment as well. Thank you in advance for help.
This isn't specific to Apartment in any way, it's more related to how DatabaseCleaner cleans your db. When using transactions, any schemas created within that transaction will be rolled back as well. Unfortunately however, you need to truncate for feature specs as transactions don't work (don't try the shared connection 'solution', it causes random failures due to a race condition). So, given that, for your feature specs, you need a way of ensuring any schemas created are deleted, since truncation only truncates tables and will NOT clean up schemas.
I'd suggest isolating your feature specs that test the multi-tenant behaviour specifically, to ensure it works the way you want it to, and manually clean up any created schemas in those specs. Then for the rest of the feature specs, assume you're testing within one tenant, or in your case one User.
We do this in our test suite, where a new Company model creates a new tenant. So we test that behaviour, for multiple tenants, then for the rest of our features we operate within 1 Company, so we don't have to worry about cleanup anymore and we can just truncate the tables within that 1 tenant. Truncate will always truncate the tables in the current tenant unless you have excluded_models.
Does that help?
Another way to deal with truncation and multi tenant applications by now is creating the tenant and deleting it on each test. Like this:
On your rails_helper.rb:
...
config.before(:suite) do
DatabaseCleaner.clean_with :truncation
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
Apartment::Tenant.drop('test') rescue nil
Company.create! name: "LittleCompany", subdomain: "test"
DatabaseCleaner.start
Apartment::Tenant.switch! "test"
end
config.after(:each) do
Apartment::Tenant.reset
DatabaseCleaner.clean
Apartment::Tenant.drop('test')
end
...
It's not very fast of course, but it's the only way i have found.
I am new to Rspec and Factory girl and would like my test to run on a specific database state. I understand I can get Factory girl to create these records, and the objects will be destroyed after the test run, but what happens if I have data in the database.
For example: I want my test to run when there are 3 records in the database that I created through Factory Girl. However, I currently already have 1 model record in the database, and I don't want to delete it just for the test. Having that 1 model in there ruins my test.
Database Content
[#<Leaderboard id: 1, score: 500, name: "Trudy">]
leaderboard_spec.rb
require 'spec_helper'
describe Rom::Leaderboard do
describe "poll leaderboard" do
it "should say 'Successful Run' when it returns" do
FactoryGirl.create(:leaderboard, score: 400, name: "Alice")
FactoryGirl.create(:leaderboard, score: 300, name: "Bob")
FactoryGirl.create(:leaderboard, score: 200, name: "John")
Leaderboard.highest_scorer.name.should == "Alice"
end
end
end
Now my test will fail because it will incorrectly assume that Trudy is the highest scorer, since the test have run in an incorrect state.
Does factory girl offer anyway to delete records from the database then rollback this delete? Similar to how it creates records in the database and rollsback
Its popular to use the database_cleaner gem. You can find it here:
https://github.com/bmabey/database_cleaner
The documentation recommends the following configuration for rspec:
RSpec.configure do |config|
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
This will you make sure you have a clean database for each test.
To answer your rollback question as directly as possible: no there isn't a way to rollback a delete within a test.
Following test conventions your goal usually is to start with a clean slate, and use factory_girl to efficiently build the scenario in the database you need to test for.
You can accomplish what you want by, for instance, adding a this to your leaderboards.rb factory file:
factory :trudy do
id 1
score 500
name "Trudy"
end
Or you could create a simple helper function in your test file that regenerates that record when its needed for tests:
def create_trudy
FactoryGirl.create :leaderboard,
id: 1,
score: 500,
name: "Trudy"
end
end
Or you could place all this in a before(:suite) within a describe block, like so:
describe "with a leaderboard record existing" do
before(:each) do
FactoryGirl.create :leaderboard, id: 1, score: 500, name: "Trudy"
end
# Tests with an initial leaderboard record
end
describe "with no leaderboard records initially" do
# Your test above would go here
end
In this final suggestion, your tests become very descriptive and when viewing the output, you will know exactly what state your database was in at the beginning of each test.
I'm a little lost with RSpec having always stuck to xUnit-based testing frameworks, but I'm giving it a go.
The nested nature of the way specs are written is giving me some headaches with regards to where I'm supposed to do database setup/teardown though.
According the to DatabaseCleaner README:
Spec::Runner.configure do |config|
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
Now I can't use transactions, because I use them in my code, so I'm just sticking to truncation, but that should be neither here nor there.
I have this:
RSpec.configure do |config|
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
The problem here is that any fixtures I create in a subject or let block have already disappeared (from the database) when I try to use them in a following describe or it block.
For example (using Machinist to create the fixtures... but that shouldn't be relevant):
describe User do
describe "finding with login credentials" do
let(:user) { User.make(:username => "test", :password => "password") }
subject { User.get_by_login_credentials!("test", "password") }
it { should == user }
end
end
I'm struggling with how I'm supposed to be nesting these describe and subject and other blocks, so maybe that's my problem, but basically this fails because when it tries to get the user from the database, it's already been removed due to the after(:each) hook being invoked, presumably after the let?
If you're going to use subject and let together, you need to understand how/when they are invoked. In this case, subject is invoked before the user method generated by let. The problem is not that the object is removed from the db before subject is invoked, but that it is not even created at that point.
Your example would work if you use the let! method, which adds a before hook that implicitly invokes the user method before the example (and therefore before subject is invoked).
That said, I'd recommend you stop struggling and use simpler API's that RSpec already exposes:
describe User do
it "finds a user with login credentials" do
user = User.make(:username => "test", :password => "password")
User.get_by_login_credentials!("test", "password").should eq(user)
end
end
That seems much simpler to me.
You wrote:
The problem here is that any fixtures I create in a subject or let block have already disappeared (from the database) when I try to use them in a following describe or it block.
That's right, that's how it works. (And you're not using fixtures in the usual Rails sense, but factories -- just as well, since Rails fixtures suck.)
Every individual spec (that is, every it block) starts (or should start) from a pristine database. Otherwise your tests would leak state and lose atomicity. So you should create every record you need within the spec in which you need it (or, as David said, in a before block to cut down on repetition).
As for organizing your specs...do it any way that makes sense. Usually there will be an outer describe block for the whole class, with inner describe blocks for groups of related behavior or specs that need a common setup. Each describe context can have its own before and after blocks. These get nested as you would expect, so the order of execution is something like
outer before
inner before
spec
inner after
outer after
If you'd like to see a project with a large number of RSpec specs and Cucumber stories (though for slightly old versions of each), check out http://github.com/marnen/quorum2 .