Passing argument to a rspec test example - ruby-on-rails

I have some tests that are using a big, test database. I'm also using Database Cleaner to clean the database after each tests. And here comes the problem.
In my spec helper I have this
config.around(:each) do |example|
MongoLib.new(database: "#{Rails.env}_sensor_data").drop_tables!
DatabaseCleaner.cleaning do
example.run
end
end
But, here's the problem. The mentioned group of tests (a big group), generates and drops this big database over and over again (once for each test). That takes a long time, and those tests does not change the database at all, so I don't really want to clean and create the database every time.
So, is there a way to do something like this:
it 'something', argument do
#testing
end
So in the spec helper I can do something like this:
config.around(:each) do |example|
MongoLib.new(database: "#{Rails.env}_sensor_data").drop_tables!
if example.argument?
DatabaseCleaner.cleaning do
example.run
end
end
end
Or maybe there is other solution for that problem? Any ideas?

You've got the right idea. Each example object in your around hook has the metadata method that returns a hash. So you can tag the tests you want to run the cleaner on, and look for that tag in your hook. Something like this:
it "does something", :db_clean do
# ...
end
config.around(:each) do |example|
if example.metadata[:db_clean]
# ...
else
# ...
end
end
You can learn more about these filters here.

Related

Rspec stub_const race condition

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.

Capybara test doesn't pass with database cleaner, must be run twice without database_cleaner to pass

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.

Seed data for test

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

RSpec + DatabaseCleaner help -- teardown happening prematurely

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 .

How to do proper database testing (TDD) on Rails 3 using MongoDB and Mongoid

How would go about writing proper unit testing (and integration testing for that matter) using MongoDB through Mongoid on Rails ?
I am asking, because to the opposite of using let's say SQLite3, even when running tests, everything I do does persists. So for the moment I am writing the creation test and then I manually delete everything I do. But it's getting annoying and even complicated to do for integration testing.
Sample of what I do:
before(:each) do
#user = User.create!(#attr)
end
after(:each) do
# MongoDB is not a transactional DB, so added objects (create) during tests can't be rollbacked
# checking for the existance of a similar object with exact :name and :email (regex make it case insensitive)
cleanup = User.where(:name => "Example User", :email => /^user#example.com/i)
cleanup.destroy unless cleanup.nil?
end
Any idea how to make MongoDB not persistent during Testing ? (I can't even run the console in sandbox mode because to use Mongoid I had to deactivate Active Record).
Ok thanks to Kyle who pointed me in the right direction, I found out how to make it work.
So basically the trick is to drop all your collections in mongodb for each test case that you will run. This is a bit radical, but it works. But keep in mind that you won't retain any data at all in you test db.
Finally I found that link: http://adventuresincoding.com/2010/07/how-to-configure-cucumber-and-rspec-to-work-with-mongoid
And basically what you need to do is simple:
add a block in you spec_helper.rb:
RSpec.configure do |config|
# blabla other confs
config.before :each do
Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop)
end
# blabla other confs
end
For Mongoid 3:
Mongoid.default_session.collections.select {|c| c.name !~ /system/ }.each(&:drop
This effectively kills all the collection within the db allowing you to run your tests fresh every time.
Alex
Another way is to use database_cleaner. It supports multiple ORMs, so I think you could do something like this:
# spec/support/database_cleaner.rb
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner[:mongoid].strategy = :truncation
DatabaseCleaner[:mongoid].clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
There's no way to make MongoDB non-persistent. You simply have to delete data before or after each test. There's some documentation on that here:
http://www.mongodb.org/display/DOCS/Rails+-+Getting+Started#Rails-GettingStarted-Testing
Here's what I did (using Test::Unit and Mongoid 3)...
# test/test_helper.rb
setup {
Mongoid.default_session.collections.select {|c| c.name !~ /system/ }.each(&:drop)
}
This one worked for me.
In spec_helper.rb, under RSpec.configure do |config| I put:
config.before :each do
Mongoid.purge!
end
Reference.

Resources