0.02
No release in over 3 years
Ensures test isolation by preventing your Rails tests from committing to the database
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Noncommittal

This gem helps you avoid test pollution in your Rails test suite by preventing tests from committing records to your Postgres database.

How to avoid commitment

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.

Do you have commitment issues?

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.

What if I want to commit to certain tables?

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])

What if I want to stop disallowing committed inserts?

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.

Limitations

This only works with Postgres currently. PRs welcome if you can accomplish the same behavior with other database adapters!

Acknowledgements

This gem is a codification of this tweet, which itself was the brainchild of Matthew Draper.

Code of Conduct

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.