RSpec::ExitGuard
RSpec carefully does not rescue SystemExit, to avoid interfering with language
mechanisms, and for philosophical reasons. The downside of that decision is that
there is a silent threat lurking in your CI - if someone writes a script and then
writes tests that load code from that script and unit-tests them, they will
eventually accidentally trigger an exit call inside the code-under-test without
having mocked exit. If it's an exit(0), rspec looks like it succeeded, CI passes,
but any tests that should have run after that one (in that same container) never
ran.
If you find your coverage numbers fluctuating wildly, or the number of tests that get run changing between runs, this is more than likely your problem. There are several ways to detect this situation, but this plugin implements my favorite of them - we mock exit/abort in a before-each hook, throw a symbol, and catch it in an around-each hook, producing a failure for the test in question.
Usage
All you need to do is install the plugin in your Gemfile, and then require it
in spec_helper.rb:
require "rspec"
require "rspec/exit_guard"
If you then call exit or abort from inside tested code, you'll see an appropriate
failure message, and the rest of the tests will still run:
F.
Failures:
1) MyService#run exits when the config file is missing
Failure/Error:
raise RSpec::ExitGuard::ExitCalled,
"#{exit_call[:call]} called at #{exit_call[:location]}"
exit(0) called at ./spec/my_service_spec.rb:12:in `block (2 levels) in <top (required)>'
Finished in 0.004 seconds (files took 0.5 seconds to load)
2 examples, 1 failure
Performance
This does add a little bit of overhead. Adding an around-each and a few mocks to every test is a measurable cost in some suites of tests. In the tests for a rails monolith though (with factories and significant database interaction) it is negligible, even for very very large test suites.