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: RSpec 3.x rails_helper.rb
and spec_helper.rb
RSpec 3.x introduces a new rails_helper.rb
convention
which contains all the Rails-specific spec configuration and leaves
spec_helper.rb
minimal, with no Rails code.
Here’s an example rails_helper.rb
:
ENV["RAILS_ENV"] = "test"
require File.expand_path("../../config/environment", __FILE__)
require "rspec/rails"
require "shoulda/matchers"
Dir[Rails.root.join("spec/support/**/*.rb")].each { |file| require file }
module Features
# Extend this module in spec/support/features/*.rb
include Formulaic::Dsl
end
RSpec.configure do |config|
config.include Features, type: :feature
config.infer_base_class_for_anonymous_controllers = false
config.infer_spec_type_from_file_location!
config.use_transactional_fixtures = false
end
ActiveRecord::Migration.maintain_test_schema!
Capybara.javascript_driver = :webkit
Here’s an example spec_helper.rb
:
require "webmock/rspec"
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.syntax = :expect
end
config.mock_with :rspec do |mocks|
mocks.syntax = :expect
end
config.order = :random
end
WebMock.disable_net_connect!(allow_localhost: true)
Using the minimal spec_helper.rb
Being conscious about what must be required for a particular source file is a good thing. Instead of loading everything but the kitchen sink, let’s specify the minimum dependencies inside of the test.
Here’s a revision of our code from above:
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 the model style_guide
.
Note that we don’t need to require spec_helper
manually in
either rails_helper
or in our StyleGuide
spec.
It is required for us in .rspec
by default:
--color
--warnings
--require spec_helper
The idea behind spec_helper
is to keep it minimal and
resist the urge to add things to it.
It should avoid becoming a junk drawer.
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.
Each test now explicitly documents 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?