Rails 6 splash screenPhoto by Alex Holyoake

The most common way for me to fire up tests in any project, especially when I’m new to it and want to test what’s already covered, how much is covered, what passes and what fails is to run just rspec in the terminal. This runs tests for all examples in the project.

During development, we usually don’t want to run the whole test suite. Most of us just want to run only tests related to changes we’ve made. This saves a lot of time.

Here are several different ways that I’ve found to run tests in isolation using RSpec:

1. By File & Directory Names

This is the commonest way to selectively run tests with RSpec. rspec takes a file name (path) or a directory name, then runs the file or the contents of the directory, both. So you can do:

rspec spec/jobs to run only test found in the jobs directory.

rspec spec/jobs spec/services to run only test found in the jobs and services directory.

rspec spec/models/user_spec.rb to only run the tests in the user_spec.rb file.

rspec spec/jobs spec/models/user_spec.rb to run all tests from the jobs folder in addition to specs found in the user_spec.rb file.

2. By Sections of an Example Group

Henceforth, let’s use the spec file and class below as our test sample.

# person_spec.rb

class Person
  attr_accessor :first_name, :last_name, :height, :eye_colour

  def initialize(first_name, last_name, height = 160, eye_colour = 'grey')
    @first_name = first_name
    @last_name = last_name
    @height = height
    @eye_colour = eye_colour
  end

  def local_greeting(greeting)
    greeting.to_s
  end
end

RSpec.describe 'Person' do
  before do
    @person = Person.new('Emmanuel', 'Hayford')
  end

  context 'identity' do
    it 'should tell first name' do
      expect(@person.first_name).to eq('Emmanuel')
    end

    it 'should tell last name' do
      expect(@person.last_name).to eq('Hayford')
    end
  end

  context 'physical features' do
    it 'should be able to tell height' do
      expect(@person.height).to eq(160)
    end

    it 'should be able to tell eye colour' do
      expect(@person.eye_colour).to eq('grey')
    end
  end

  context 'culture' do
    it 'should be able to greet in local language' do
      local_greeting = @person.local_greeting('Maakye')

      expect(local_greeting).to eq('Maakye')
    end
  end
end

RSpec easily allows to just pick one section (or several) of an example group to run. This allows us to run something like rspec person_spec.rb:21 where 21 is the line number of exactly the section we want to run. The line number could fall on any of these RSpec methods: describe, context, it or expect and RSpec will smartly run just that section. If the line falls on an empty line, RSpec will run the closest (from the top) it block of the test file. And if the empty line is the above all the test examples, then RSpec will just run all the tests!

We can also run a section of tests by doing rspec person_spec.rb[1:3]. The [x:y] format instructs RSpec to run the xth group (in our case we have just one group… which is everything inside the RSpec.describe 'Person' block) and the yth example block. So running rspec person_spec.rb[1:3] will run the 'culture' block. You can do multiple sections like rspec person_spec.rb[1:1,1:3] for example which will then run just the 'identity' and 'culture' blocks.

3. Through the Description of an Example

You can run a test like so rspec person_spec.rb -e "greet". This will run all examples/groups that have “greet” in their description. The -e flag is short for --example. It is worth noting, however, that this is case sensitive so rspec person_spec.rb -e "greet" and rspec person_spec.rb -e "Greet" won’t yield the same results.

Running rspec person_spec.rb -e "greet" -fd in on our spec file will return

Run options: include {:full_description=>/greet/}

Person
  culture
    should be able to greet in local language

Finished in 0.00088 seconds (files took 0.09423 seconds to load)
1 example, 0 failures

There’s only one example that has “greet” in its description.

4. By Focusing on an Example

This feature is very useful if you’re working on just one test or a group that you’re working. To use this feature you have to go an extra step with your configuration. The configuration in question is

RSpec.configure do |config|
  config.filter_run_when_matching focus: true
end

In a typical Rails project, this might go in your spec_helper.rb file. Once this configuration is in place you can add f to describe, context or it so it becomes fdescribe, fcontext or fit and RSpec will “focus” on that example/group. Doing

fit 'should tell height' do
  expect(@person.height).to eq(160)
end

is equivalent to

it 'should tell height', focus: true do
  expect(@person.height).to eq(160)
end

5. By Running Only Failing Tests

RSpec has rspec file_name_spec.rb --only-failures. This comes in handy when you’re only interested in running tests that fail so you can work on fixing them. But to keep track of which tests failed the last time you run the tests, RSpec needs yet another configuration that will tell it when to store information on which tests failed/passed.

This configuration should look like

RSpec.configure do |config|
  config.example_status_persistence_file_path = 'some_file.txt'
end

If you’re working with Git, you might want to add some_file.txt to .gitignore.

It’s important to run this file once without any flags to record the statuses of all examples. This is how the content looks like after we run our tests for the first time with some (deliberate) failures.

Running only failing RSpec tests

Once the first run has recorded the statuses of the tests we can then run rspec person_spec.rb --only-failures to just run the failing tests and re-run after we’ve made changes to our code to see if the failure we’re tracking has been fixed, if it has, its status will change to “passed” in the file RSpec is configured to do the tracking.

6. By Running Failing Tests (One-At-A-Time)

If there are multiple failures with rspec person_spec.rb --only-failures from the above, you can do rspec person_spec.rb --next-failure to repeatedly run a single failure at a time. Neat!

7. By Tag Filtering

You can tag examples/groups with a hash (RSpec calls this “metadata”, the focus: true in the tests you saw above is a metadata example). The hash can contain arbitrary key/values of your choice. For example, let’s say we want to mark some tests as important. We can do so by altering our tests to add a important: true so it looks like the following:

RSpec.describe 'Person' do
  before do
    @person = Person.new('Emmanuel', 'Hayford')
  end

  context 'identity', important: true do # 👈
    it 'should tell first name' do
      expect(@person.first_name).to eq('Emmanuel')
    end

    it 'should tell last name'  do
      expect(@person.last_name).to eq('Hayford')
    end
  end

  context 'physical features', important: false do # 👈
    it 'should be able to tell height' do
      expect(@person.height).to eq(160)
    end

    it 'should be able to tell eye colour' do
      expect(@person.eye_colour).to eq('grey')
    end
  end

  context 'culture', important: true do # 👈
    it 'should be able to greet in local language' do
      local_greeting = @person.local_greeting('Maakye')

      expect(local_greeting).to eq('Maakye')
    end
  end
end

With this change in place we can run rspec person_spec.rb --tag important and expect to have only 3 examples running instead of 5.

Run options: include {:important=>true}
...

Finished in 0.00721 seconds (files took 0.09502 seconds to load)
3 examples, 0 failures

Armed with these I’m sure we can cumulatively save a lot of time, time that we can invest doing other things. Selectively picking tests you want will speed up development and I highly recommend doing this with real work.

Let me know in the comments if you have other ways to save time or selectively run examples with RSpec. You can follow me on Twitter for Ruby-related tweets.