In Progress
Unit 1, Lesson 1
In Progress

Eliminating RSpec Boilderplate with Claudio Baccigalupo

One of the attributes that makes developing in Ruby attractive is the wide array of internal, declarative DSLs for common tasks such as testing. But the clarity of these DSLs is quickly diminished when they are crowded with a bunch of boilerplate setup code. In today’s episode, guest chef Claudio Baccigalupo shows us how to eliminate some common boilerplate code in RSpec system specs. Enjoy!

Video transcript & code

Today I want to show you a few pointers for eliminating boilerplate and clarifying RSpec tests, especially for Rails projects. Some of the things we’ll talk about are part of the default configuration that RSpec generates for Rails projects. But today we’re going to set things up from scratch, so we can learn to understand these features a little better. Ready? Let’s dive in!

Many "private only" web applications have a home page that checks if you are logged in. If you are not, they display a link for you to log in. Since this is an important feature, let's write a system test for it, using RSpec.


One way to write such a test is to put everything in a single file, like this:

# spec/home_spec.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'

RSpec.describe 'The home page', type: :system do
  it {
    visit root_url
    expect(page).to have_link('Log in with your email')
  }
end

  • we include the requirements for rspec/rails
  • we create a test of type "system", so we can access Capybara methods and Rails routes
  • and then we write our expectation: the "root" page should have a link that says "Log in with your email"

Let's run the test: it passes!

006

We could stop right here. Sure we could, but… we are Rubyists, we like our code to work and to be clean and readable, and I'm not really happy with this file. It's not focused on the expectation enough, it has too much noise in the code and also in the output. Let's make this code better in four steps.


Step number one: let's move all the requirements in a separate file called 'rails_helper':

# spec/rails_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'

so we can reduce our overhead in our spec file to a single line of requirements:

# spec/home_spec.rb

require 'rspec_helper'

RSpec.describe 'The home page', type: :system do
  it {
    visit root_url
    expect(page).to have_link('Log in with your email')
  }
end

Step number two: can we even get rid of this single "require" line? We can!

We just need to create a ".rspec" file with some default command-line arguments. Here we specify that rspec should always load the rails_helper.rb file.

# .rspec

--require rails_helper

This file is loaded before any step requiring the helper so we don't need to specify that in the test:

# spec/home_spec.rb

RSpec.describe 'The home page', type: :system do
  it {
    visit root_url
    expect(page).to have_link('Log in with your email')
  }
end

This can save us some boilerplate when writing our spec files. Just be aware that using .rspec to load helper files will mean that those files are loaded when running any of our specs. If you have some tests that don’t depend on loading a full Rails environment and you those specs to run faster, you may want to explicitly choose between separate spec_helper and rails_helper files in your spec files.


Step number three: can we get rid of that "type: :system" in our test?

If we simply delete it,

Rails will complain that routes like "root_url" are not available:

003

There is a solution though: we can ask Rails to infer the spec type from its file location.

To do that, we move the test to a subfolder called "system"

and we enable this configuration in the helper. We use the “infer spec type from file location” configuration directive to tell RSpec to guess what kind of test this is from the name of the directory it’s in.

# spec/rails_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

Done!

Now Rails will know that every file in the system/ folder is indeed a System test, without any additional metadata:

# spec/system/home_spec.rb

RSpec.describe 'The home page' do
  it {
    visit root_url
    expect(page).to have_link('Log in with your email')
  }
end

Step number four: this file is now short, but it still doesn't fully focus on the expectation. That's because the it block is doing two consecutive things: visiting the Root url and then checking that the text is there. To clarify that visiting the URL is a precondition, my suggestion is to:

  • move that into a Before block
  • add an empty line after that
  • give an explicit description to the "it" block
# spec/system/home_spec.rb

RSpec.describe 'The home page' do
  before { visit root_url }

  it 'displays a link to log in' do
    expect(page).to have_link('Log in with your email')
  end
end

This makes it clear that "The home page" is tied to visiting the root URL and clarifies exactly our expectations.


If we want to see these verbose expectations in the output, all we have to do is to pass the --format documentation option to the rspec command:

005

Notice how this output is more helpful to the reader than just a series of red and green dots.

You can avoid typing --format documentation every time and rather set it by default by adding it to the .rspec file.


In conclusion, our code looks like this:

# .rspec

--require rails_helper
--format documentation
# spec/rails_helper

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end
# spec/system/home_spec.rb

RSpec.describe 'The home page' do
  before { visit root_url }

  it 'displays a link to log in' do
    expect(page).to have_link('Log in with your email')
  end
end

I hope this was helpful in understanding not only how to write system specs in Rails, but also how to structure your code to make it more readable and focused. Thanks for watching. Happy hacking!

Responses