Database-bound tests are a drag. Inconsistent tests are a pain. Database-bound, inconsistently failing tests are the worst!
The following commit message is from a real code base:
Run in transactions by default.
When we added controller specs they weren't being run w/any kind of DB cleaner b/c there was no default strategy and they weren't explicitly included in a group. Now, we use
:transactionsbe default, setting request specs to use
Also, I saw a 2 second speed up from this change!
Let's look at what we changed in this commit to turn our inconsistently failing database-bound tests into slightly faster, consistent, database-bound tests.
Prefer isolated tests
Whenever possible I strive to write isolated tests, stubbing out collaborators where necessary, while driving from the outside of the system downward, one layer at time. However, there are occasions where you must hit the database.
ActiveRecord scopes and higher-order acceptance tests are two cases
where I believe it is okay to have tests which cross layers, and may even hit a
Ensuring idempotent tests
When you are going to hit a database, your tests should be good citizens and clean up after themselves.
If you're using RSpec with Rails your tests run within a transaction by default. Yay!
Unfortunately, test tooling like Capybara won't work with transactions and you'll be forced to resort to techniques like database truncation to ensure proper data clean up. I really like Database Cleaner for that job.
An example RSpec configuration
What follows is the RSpec configuration we ended up with after the previously mentioned commit. Or at least a very close approximation of it
A few key take aways:
To start, turn off RSpec's built-in transaction support as we'll handle that
Next we configure a default clean-up strategy for every RSpec suite. We'll use transactions by default, being sure to clean up any thing that might have been left behind with a truncation.
We make a special exception for request specs, which are often driven by Capybara, and switch them over to truncation.
Then, before every test runs start Database Cleaner
And finally, after every test runs, tell Database cleaner to clean up using whatever strategy it is currently configured with.