What is RSpec?
RSpec is a domain-specific language testing tool written in Ruby to test Ruby code. It is a behavior-driven development framework, used for the most part in Rails applications, although you can use it outside of Ruby on Rails and without any framework as we’ll see in this post.
How to install RSpec
You can install RSpec in a couple of ways, one way is to install it directly into your gem path by running $ gem install rspec
. When you install RSpec you’ll get 5 other supporting gems installed for you. Your output might look something like this:
Fetching rspec-core-3.11.0.gem
Fetching rspec-3.11.0.gem
Fetching rspec-support-3.11.1.gem
Fetching diff-lcs-1.5.0.gem
Fetching rspec-expectations-3.11.1.gem
Fetching rspec-mocks-3.11.1.gem
Successfully installed rspec-support-3.11.1
Successfully installed rspec-core-3.11.0
Successfully installed diff-lcs-1.5.0
Successfully installed rspec-expectations-3.11.1
Successfully installed rspec-mocks-3.11.1
Successfully installed rspec-3.11.0
6 gems installed
Another way to install RSpec is to run $ bundle init
in the terminal from your working directory. $ bundle init
generates a Gemfile in the current working directory. If you take this route to install RSpec, you can add gems manually to the Gemfile or do $ bundle add rspec
which will add RSpec to your Gemfile and install it for you. At this point, you have RSpec installed and you’re ready to take it for a spin.
Test-Driven Development (TDD) with RSpec
In the spirit of TDD, we’ll write a failing test first describing what we want our Ruby program to do, then run the tests, see it fail and then proceed to fix it. To start, you can run this command from your working directory in your terminal:
$ rspec --init
This command gives you an initialization point from which to build your tests. Running this command give us a a directory and a .rspec
file. Inside of the directory is another file, spec_helper.rb
. Our working directory should look like this:
tree -a
.
├── .rspec
└── spec
└── spec_helper.rb
1 directory, 2 files
If you look inside spec_helper.rb
, everything you need to know about these generated files is stated:
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
With that, let’s write our first spec:
RSpec.describe Human, "#greet" do
it "should greet successfully" do
human = Human.new
expect(human.greet).to eq "Hello, world!"
end
end
The content of the spec above is in human_spec.rb
. The conversion is to prefix _spec.rb
with whatever class or object you want to test. In our case we want to test a Human
class whose instances can greet
.
The first line is a class method describe
, that describes the Human
class we want to test, and the method in the Human
class that we want to describe, #greet
.
Our directory now looks something like this:
tree -a
.
├── .rspec
└── spec
├── human_spec.rb
└── spec_helper.rb
1 directory, 3 files
At this point, when we run our tests, things should fail:
rspec
An error occurred while loading ./spec/human_spec.rb.
Failure/Error:
RSpec.describe Human, "#greet" do
it "should greet successfully" do
human = Human.new
expect(human.greet).to eq "Hello, world!"
end
end
NameError:
uninitialized constant Human
# ./spec/human_spec.rb:2:in `<top (required)>'
No examples found.
Finished in 0.00003 seconds (files took 0.30025 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
The spec is failing. We’ve got an uninitialized constant Human
. We haven’t created that class yet. Let’s create that class and require it.
We require the Human
class and so our file looks like this:
require './human'
RSpec.describe Human, "#greet" do
it "should greet successfully" do
human = Human.new
expect(human.greet).to eq "Hello, world!"
end
end
Directory structure is now:
tree -a
.
├── .rspec
├── human.rb
└── spec
├── human_spec.rb
└── spec_helper.rb
1 directory, 4 files
After adding a Human
class and running the specs again, we have:
rspec
F
Failures:
1) Human#greet should greet successfully
Failure/Error: expect(human.greet).to eq "Hello, world!"
NoMethodError:
undefined method `greet' for #<Human:0x00007f9414902c70>
# ./spec/human_spec.rb:7:in `block (2 levels) in <top (required)>'
Finished in 0.00185 seconds (files took 0.10377 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/human_spec.rb:4 # Human#greet should greet successfully
This time we have an undefined method 'greet' for #<Human:0x00007f9414902c70>
.
From this point, defining the greet
class is all we need to get our specs passing:
class Human
def greet
"Hello, world!"
end
end
rspec
.
Finished in 0.00206 seconds (files took 0.10401 seconds to load)
1 example, 0 failures
This completes our quick introduction to setting put and running RSpec. Running RSpec alone is a lot more involved. I have a post on how to run RSpec tests. In a big codebase, you might want to pick out specific tests to run to save resources.