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:
- File & Directory Names
- Sections Of An Example Group
- The Description Of An Example
- Focusing On An Example
- Running Only Failing Tests
- Running Failing Tests One-At-A-Time
- Tag Filtering
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: describe
, context
, it
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 x
th group (in our case we have just one group… which is everything inside the RSpec.describe 'Person'
block) and the y
th 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 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. 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.
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.