This gem helps you avoid test pollution in your Rails test suite by preventing tests from committing records to your Postgres database.
Add this to your Gemfile, probably grouped with other test gems:
group :test do
gem "noncommittal"
end
Then, in your test_helper.rb
(or similar), after you require your Rails
environment, just drop this in:
Noncommittal.start!
This will create an empty table called noncommittal_no_rows_allowed
and, for
every table in your database, a deferred foreign key constraint that will
effectively prevent any records from being committed outside the test
transaction.
By default, Ruby on Rails tests run each test in a transaction that is rolled back at the end of each test. This is a performant way to create proper isolation, preventing tests that save records to the database from affecting the environment of your other tests.
However, Rails can only enforce this constraint when the test-scoped code
connects to the database via the framework and within that transaction. If a
test were to extend Minitest::Test
instead of ActiveSupport::TestCase
, that
test would not benefit from this rollback-only transaction protection. And if
the subject under
test contains
transaction logic itself, or creates its own database connections, or spawns
child processes, then it's entirely possible that some of your tests will
erroneously commit records to the database, potentially causing test
pollution.
Over the years, Rubyists have taken several approaches to mitigate this risk, but most popular solutions have drawbacks:
- Gems like database_cleaner that purge your database between tests introduce a per-test runtime cost that is much higher than relying on transaction rollbacks
- Bisecting your test suite to identify which test that violates transaction safety catches test pollution only after it causes a problem and is separately time-consuming
- Adding a before-hook that runs before each test to ensure tables are empty won't give you a stack trace to the test that managed to commit inserted records
So that's why the noncommittal gem exists! It's fast, will catch this problem as soon as it's introduced, and will give you an accurate stack trace to the offending test.
By default, noncommittal will gather the table names of all your models that
descend from ActiveRecord::Base
, but this may not be what you want (you might
want to exclude certain models or include additional tables). To override this
behavior, you can pass an array of table names to a tables
keyword argument,
like so:
Noncommittal.start!(tables: [:users, :posts])
If you simply want to exclude certain tables, you can set the exclude_tables
keyword argument:
Noncommittal.start!(exclude_tables: [:system_configurations])
Just call Noncommittal.stop!
, which takes the same arguments as start!
for
specifying tables and exclusions, and will simply remove all the constraints
and the reference table.
This only works with Postgres currently. PRs welcome if you can accomplish the same behavior with other database adapters!
This gem is a codification of this tweet, which itself was the brainchild of Matthew Draper.
This project follows Test Double's code of conduct for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories.