Master RSpec with command line

Using RSpec can be a lot more effective and fun if you know well how many options you have at your disposal. Usually, you run the test using the following command:

rspec spec/your_class_spec.rb

alternately with the option for running multiple files at once:

rspec spec/your_class_spec.rb spec/directory/ spec/another_class_spec.rb

Above commands are standard, but In many cases, this is enough. But in Ruby, too much awesomeness is not enough awesomeness. This article will cover more advanced usage of the rspec command and less known options but still useful.

If you are not yet familiarized with RSpec, make sure you saw the introduction article, which is a quick introduction to testing Ruby code. Before we begin exploring the command-line RSpec world, I will create a simple test on which we will be testing the different command-line options. Such a real-world example will show you how you can become more effective with testing by using more advanced options.

Given we have the following Person class:

class Person
  def initialize(age:)
    @age = age
  end

  def adult?
    @age > 17
  end
end

And equally simple test in the person_spec.rb file:

require 'spec_helper'

RSpec.describe Person do
  describe '#adult?' do
    it 'returns false if the given person is less than 18 years old' do
      person = Person.new(age: 16)

      expect(person.adult?).to eq(false)
    end

    it 'returns true if the given person is more than 17 years old' do
      person = Person.new(age: 19)

      expect(person.adult?).to eq(true)
    end
  end
end

We can run it using the standard rspec command:

rspec spec/person_spec.rb

The custom output of the tests

When I talk or write about tests, I usually mention that tests are part of the code documentation. Sure, you can just open test files and look through them, but thanks to the --format option, you can run the test and receive an output that looks like documentation.

By default, the tests’ output consists of dots for passed tests, starts for pending tests, and F characters for those who failed. You can change this behavior by passing the --format option:

rspec spec/ --format documentation

Thanks to the above command instead of the default output:

..

You will receive the following format:

Person
  #adult?
    returns false if the given person is less than 18 years old
    returns true if the given person is more than 17 years old

In the next paragraph, I will show you a cool combination with this option that will allow you to display the "documentation" only for the given method. And just in case you would need to save this piece of documentation for later - you can save the output in the file by using the --out option:

rspec spec/ --format documentation --out rspec_documentation.txt

Filtering tests by example name

The --example option allows you to run only those tests that are matching the given string. The argument is matched against the full description of the example. The full description is the concatenation of descriptions of the group, including any nested groups and contexts.

If you would like to run only those tests that test if the #adult? method returns true, you could do the following:

rspec --example ‘#adult? returns true`

With the combination of --format option, you can even get a list of cases when the method returns true:

rspec --example ‘#adult? returns true` --format documentation

In our case, the output will be the following:

Person
  #adult?
    returns true if the given person is more than 17 years old

You don’t even have to open the code editor to answer the question; cool, isn’t it?

Filtering tests by tag name

If you would like to tag some examples that belong to the given feature, you can quickly run them all at once by using the --tag option. Let’s assume that our spec belongs to the authentication feature:

RSpec.describe Person do
 describe '#adult?', feature: 'authentication' do
   it 'returns false if the given person is less than 18 years old' do
     person = Person.new(age: 16)

     expect(person.adult?).to eq(false)
   end

   it 'returns true if the given person is more than 17 years old' do
     person = Person.new(age: 19)

     expect(person.adult?).to eq(true)
   end
 end
end

When you have many tests, and their execution takes much time, you don’t want to select manually all specs related to the feature you are working on. To run only tests related to authentication, you can use the following command:

rspec spec/ --tag feature:authentication

If would have cases where one spec belongs to more than one feature, than there is no need to worry; arrays of arguments are also supported:

require './person'
require 'spec_helper'

RSpec.describe Person do
 describe '#adult?' do
   it 'returns false if the given person is less than 18 years old', feature: ['authentication', 'other'] do
     person = Person.new(age: 16)

     expect(person.adult?).to eq(false)
   end

   it 'returns true if the given person is more than 17 years old', feature: 'authentication' do
     person = Person.new(age: 19)

     expect(person.adult?).to eq(true)
   end
 end
end

You can still use the --tag option as before and filter tests:

rspec spec/ --tag feature:authentication # will run two tests
rspec spec/ --tag feature:other # will run one test

Filtering tags with boolean value is also possible, and it’s even easier. If you usually mark slow tests like this:

it 'returns false if the given person is less than 18 years old', slow: true do
 person = Person.new(age: 16)

 expect(person.adult?).to eq(false)
end

You can run only slow tests by using a special syntax:

rspec spec/ --tag @slow

Which is a shortcut for --tag slow:true

You saw a few examples that let you filter the tests by their tags, but how about excluding some tests knowing their tag or tags? It’s as easy as adding ~ before the tag value:

rspec spec/ --tag ~@slow

The above command will run all specs except those marked as slow. Feel free to use other combinations of --tag with the exclude character.

Running specs… without running the tests

At first, it sounds a little bit weird, but yes, it’s possible to run tests without running them. It’s called a dry run, and you could meet this term before in the software development. The dry run means that you run the process, but it does not affect anything.

If you have a large tests codebase and you would like to see how many specs are there or how many are pending, you can perform a dry run that won’t trigger the tests, but the rspec will behave as we would run them:

rspec spec/ --dry-run

In the large codebase, the output could be the following:

Finished in 0.10594 seconds (files took 20.68 seconds to load)
1429 examples, 0 failures, 5 pending

Nice, it would be great to execute almost 1,5k of tests in less than one second. Another good example of --dry-run usage is the case where you would like to see the documentation for a given method but without running the tests:

rspec spec/ --format documentation --dry-run --example “#instance_method”

Failing fast

If you are working on fixing failures in the test suite, you will appreciate the --fail-fast option. If you look for a first failure and don’t want to continue after it appears, use the following command:

rspec spec/ --fail-fast

The RSpec won’t continue running tests after the first failure. If you want to stop after more than one failure, you can specify the number of failed tests after which the tests should be stopped:

rspec spec/ --fail-fast=6

By default, the RSpec won’t stop on the first failure and will continue running tests until all requested tests are executed.

Focusing on failures

If we are talking about the RSpec’s support for failing tests, it is worth mentioning the --only-failures option. If you are working on fixing failures, you can save the failures into a file and then, with the --next-failure option, jump to the next failing example after you fix the current one.

To make usage of --only-failures option, you have to change the RSpec’s config and specify the file where the library will store recent errors:

RSpec.configure do |c|
  c.example_status_persistence_file_path = "failures.txt"
end

Now, when you execute your tests, RSpec will save failing tests in the failures.txt file:

example_id                   | status | run_time        |
---------------------------- | ------ | --------------- |
./spec/person_spec.rb[1:1:1] | failed | 0.01228 seconds |
./spec/person_spec.rb[1:1:2] | failed | 0.00015 seconds |

You can now run the first failing test:

rspec spec/ --next-failure

or run all failing tests:

rspec spec/ --only-failures

For those who deal with random failures

One more thing related to the failures. Sometimes when you run the whole test suite, one spec is failing, but when you execute only this one spec, it’s passing - this may be confusing, but it’s not something that the RSpec creators were not aware of.

With the --bisect option, RSpec will run your tests to find the minimal number of examples needed to reproduce the failure. With such information, it will be easier to find the place in the test where the data is overwritten, and the result of the test is different from the one received when one test is executed.

Summary

If you would like to see all available command-line options for RSpec, make sure you visited the official documentation.

The knowledge about command-line options can improve your workflow and make it faster to work with the RSpec tests.