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.