Failing an RSpec suite on poor code coverage.

Background

I'm just starting up a new project and stared with setting up the test harness. For me it is the usual suspects of RSpec, Guard and Simplecov. I have done this many times before, normally with Rails, but this is a non Rails ruby server written with Rack so it differs on some points.

While working on the setup I had the opportunity to dive deeper into RSpec configuration than I usually do. Since I didn't include the RSpec::Rails gem this time my request specs didn't automatically become of type :request for example.

While fiddling with all of the other settings I started to wonder if there where some easy way to have the suite fail if the coverage was too low?

Some googling didn't yield much so I started hacking and reading source on my own instead. This is the result:

require 'simplecov'

SimpleCov.start

RSpec.configure do |config|
  config.after(:suite) do
    example_group = RSpec.describe('Code coverage')
    example = example_group.example('must be above 90%'){
      expect( SimpleCov.result.covered_percent ).to be > 90
    }
    example_group.run

    passed = example.execution_result.status == :passed

    RSpec.configuration.reporter.example_failed example unless passed
  end
  config.order = :random
end

Breakdown

The config.after(:suite) is a block of code that RSpec will run after the entire rest of the suite has finished. We need to wait until then since if we did this in the normal flow, and since the config.random option is on, we can't be sure all the code has been measured before we check the coverage.

The meat of the after block is basically creating a new example group with one example in it. This is the same thing that actually happens when RSpec loads you *_spec.rb files.

We then run the example group and inspect the result. If we have a failure we notify RSpec about it and if it passed we just exit scilently.

Finally

This could be cleaned up a bit more and for example notify RSpec if the test passed as well. But for now I got what I wanted:

  • The test suite fails if the coverage goal is not met
  • The failure is logged as an RSpec fails test

This means that any normal CI environment will fail the build on low coverage since that now fails the test suite. Excellent :)