The most common way to run tests in a project is to run rspec in the terminal. This runs tests for all examples in the project. Sometimes, this is not what we want.

During development, we don’t want to run the whole test suite every time after a change. Most of us want to run only tests related to changes we’ve made to save time.

If you’re new to RSpec, here’s a quick introduction to testing with RSpec.

We can run specific RSpec tests by:

1. File & Directory Names

Running tests by their file or directory names is the most familiar way to run tests with RSpec. RSpec can take a single file name or directory name and run the file or the contents of the directory.

How to run a single RSpec test file

To run a single Rspec test file, you can do the following to run the tests in the your_spec.rb file:

$ rspec spec/models/your_spec.rb

If you want to run a single RSpec test file in addition to other test files in another folder, RSpec allows you to pass multiple arguments to it. For example, you can do $ rspec spec/jobs spec/models/your_spec.rb to run all the tests from the jobs folder in addition to specs found in the your_spec.rb file.

How to run all Rspec test files in a directory

Similarly, you can pass as an argument to the rspec command the path to a folder or folders to have RSpec run all the test files in the folder to run the tests found in the jobs directory like this:

$ rspec spec/jobs

And to run only tests found in the jobs and services directory:

$ rspec spec/jobs spec/services

2. 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 allows picking one (or several) section of an example group to run, allowing us to run something like $ rspec person_spec.rb:21 where 21 is the line number of the section we want to run. The line number could fall on any of these RSpec methods: describecontextit or expect and RSpec will run that section. If the line falls on an empty line, RSpec will run the closest (from the top) it block of the test file. If the blank line is above all the test examples, RSpec will 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 and it will run just the 'identity' and 'culture' blocks.

3. 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 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. Focusing On An Example

This feature is handy 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 describecontext or it so it becomes fdescribefcontext 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. Running Only Failing Tests

RSpec has $ rspec file_name_spec.rb --only-failures, very 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 where 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 essential 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 run just the failing tests and re-run after we’ve made changes to our code to see if we’ve been able to fix the failure we’re tracking, if it has, its status will change to “passed” in the file that we configured for RSpec to do the tracking.

6. 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. 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 an 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 save a lot of time, time that we can invest in doing other things. Picking tests you want will speed up development. I recommend doing this with real work.

Last Update: January 09, 2024