Reputation: 547
I am building a Rails App (I am new to this, so forgive me if some of the wording is clumsy). I am trying to write tests (with RSpec) which draw and use data from the database, and I am having trouble writing the tests in a concise way.
Some of the tests (such as signing up a user, or creating content) seem to be best suited to having a fresh database, while some require a database populated fixtures.
At the moment I am using the database cleaner gem, with the following configuration:
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
DatabaseCleaner.strategy = :truncation end
# start the transaction strategy as examples are run
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
The reason I am using truncation
for both strategies is that I prefer to have the id
values completely refreshed between examples (so that if I create
in one test, and then create
in a second test, the second example should have id 1
rather than 2
). I am not sure what the various strategies mean exactly - I have found this question which appears to explain them in terms of SQL syntax, but I'm not very familiar with that so my understanding is still quite vague. I believe the database is
managed with PostgreSQL, but I rarely have to interact with it directly through that so I'm not particularly experienced.
So my database is completely dropped and constructed from scratch between every example - if I want a clean database then this is ideal, but if I want to simply load the fixtures then it can take a while to create all of the models. It feels like I should be able to have a 'cached' version of the fixtures, which I can load for those examples it's appropriate for. But I have no idea how to do this, if it is even possible. Is there a way?
Edit: Following a discussion in the comments, I suspect that I may want to remove Database Cleaner and use default Rails fixtures instead. I have tried this, and the only problem I'm having with it is the same as I had with the transaction
strategy described above. That is: when test-created records are rolled back, the id
is not rolled back, and this is awkward behaviour. If I create a user
for the purpose of running a test, it is convenient to refer to it just as User.find(1)
, which is impossible if the id
does not reset.
It might be that this is some kind of red flag, and I shouldn't be doing it (I am open to doing something else). I realise too that I could just say User.first
to get the same behaviour, and this might be better. I'm not sure what's appropriate.
Upvotes: 1
Views: 1179
Reputation: 102134
DatabaseCleaner is not intended to be used with fixtures. Its intended to be used with factories. ActiveRecord::Fixtures
has its own rollback mechanism.
There is a really big conceptional difference.
Fixtures are like this huge set of static dummy data that gets thrown into the database for each example and then gets reset with a transaction. The big con of fixtures is that the more fixtures you have the more complex the initial state of the application is and it encourages a tight coupling between the tests and the fixtures themselves.
This is an example which shows how the value "Marko Anastasov" magically appears from somewhere outside the code:
RSpec.describe User do
fixtures :all
describe "#full_name" do
it "is composed of first and last name" do
user = users(:marko)
expect(user.full_name).to eql "Marko Anastasov"
end
end
end
Although fixtures have hade a resurgence lately due to perceived simplicity (along with Minitest).
Factories are object factories that produce unique records. Instead of having a bunch of junk floating around you start each example with a blank state and then use the factories to populate the database with the exact state to replicate the scenario you are testing. Done right this minimizes test ordering issues, flapping tests and changing fixtures breaking tests.
RSpec.describe User do
describe "#full_name" do
it "is composed of first and last name" do
user = FactoryBot.create(:user)
expect(user.full_name).to eql "#{user.first_name} #{user.last_name}"
end
end
end
This is an example of a good factory that would generate psuedorandom values:
require 'ffaker'
FactoryBot.define do
factory :user do
first_name { FFaker::Name.first_name }
last_name { FFaker::Name.last_name }
end
end
Upvotes: 1