GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

Document Explicit Dependencies Through Tests

One of the purposes of writing tests is to provide living documentation of application’s code. Tests provide real examples of how a certain class or function is supposed to be used. Tests could also document the exact dependencies of the tested code.

The Problem

When Rails boots it loads most, if not all, of the application’s code, along with all of the dependencies (gems). Because of this, there is no need to require dependencies in individual files that contain application logic. When looking at the source of a specific class, it is hard to tell what external code it depends on. The test doesn’t help either.

A typical RSpec test usually looks something like this:

require 'spec_helper'

describe StyleGuide do
  # actual tests omitted
end

In the Rails community, it has become a de facto standard to require the default spec_helper (or an equivalent) in each test file. A typical spec/spec_helper.rb file ends up loading the whole Rails environment, requiring numerous helper files, and setting up various configuration options. All of that, en masse, is more than what any particular test file needs.

Certainly, integration and feature tests depend on the entire Rails environment. ActiveRecord model tests depend on the presence and configuration of a database. These are all good use cases for spec_helper. But what about unit tests that don’t require the database? When testing a plain old Ruby class, there might only be a few dependencies, if any.

The Solution

Being conscious about what must be required for a particular source file is a good thing. Instead of loading everything but the kitchen sink through the spec_helper, let’s specify the minimum dependencies inside of the test.

Here’s a revision of our code from above:

require 'basic_spec_helper'
require 'rubocop'
require 'app/models/style_guide'

describe StyleGuide do
  # actual tests omitted
end

This code states exactly what the tested class (app/models/style_guide.rb) depends on: the gem rubocop and basic_spec_helper.rb file.

The idea behind basic_spec_helper is that it’s very minimal, and that its sole purpose is to do a few convenience things. It should avoid becoming a junk drawer like spec_helper.

Here’s an example of a basic spec helper, extracted from Hound:

$LOAD_PATH << File.expand_path('../..', __FILE__)

require 'webmock/rspec'

RSpec.configure do |config|
  config.expect_with(:rspec) { |c| c.syntax = :expect }
  config.order = 'random'
end

This lightweight spec helper does just enough to enforce good practices and setup some configuration that should apply to all of the tests. Here’s the quick breakdown of what this spec helper is doing:

  1. Add the project root to the load path to allow requiring files starting at the project root.

    $LOAD_PATH << File.expand_path('../..', __FILE__)
    
  2. Requires webmock/rspec to ensure that external requests are not allowed.

    require 'webmock/rspec'
    
  3. Provides preferred RSpec configuration that all tests should adhere to.

    RSpec.configure do |config|
      config.expect_with(:rspec) { |c| c.syntax = :expect }
      config.order = 'random'
    end
    

Tests which might be part of a Rails application test suite, but don’t actually depend on Rails or ActiveRecord can now require this basic spec helper along with the essential gems and files. This causes each test to explicitly document dependencies of the tested code. Loading minimal dependencies during tests removes any magical coupling, helps with refactoring, saves time during debugging, and makes tests run faster.

What’s Next?

Want to learn more techniques about decoupling code away from Rails?