Would you like to learn how to write tests for your Ruby applications using RSpec?
Then you’re in the right place!
In this tutorial I’ll show you how to do that.
Contents
Why Should You Write Tests?
Here’s why:
It builds a safety net against errors (especially useful for refactoring)
If you don’t have a test suite then you don’t want to touch your code, because of the fear of something breaking…
…having tests increases your confidence!
It helps document your code
Your tests describe what your application should be doing.
It gives you a feedback loop
When you are doing TDD you get a feedback loop that tells you what to focus on next, useful if you get distracted easily.
It helps you make sure your code is producing the results you expect
This one is important!
If you are writing some complex logic, then you want to make sure it’s working with many different inputs & not just with one example you came up with.
Tests can help you uncover corner cases & document them.
It helps you land a Ruby job
Most job applications will appreciate your testing skills, increasing your chances of landing the job.
Getting Started With RSpec
To understand how RSpec works let’s go over an example step-by-step.
We are going to write a simple application that finds factorial numbers.
The first step:
require 'rspec/autorun' describe Factorial do # ... end
This is the initial code for writing your first RSpec test.
You need to require the rspec gem.
Then you need to create a describe
block to group all your tests together & to tell RSpec which class you are testing.
Next is the it
block:
describe Factorial do it "does something" do # ... end end
This is the test name, plus a way to group together all the components of the test itself.
The components are:
- Setup
- Exercise
- Verify
The setup is where you create any objects that you need to create.
It’s the preparation phase.
Then you call the method you want to exercise to get its return value.
Finally, you verify the result with an expectation (RSpec) or assertion (Minitest).
RSpec Testing Example
Now if we want to write a factorial method, we have to find out some valid values online or by hand calculation.
Then we use those with our tests.
Like this:
describe Factorial do it "finds the factorial of 5" do calculator = Factorial.new expect(calculator.factorial_of(5)).to eq(120) end end
When you run this code (like a regular Ruby program) you’ll get this:
uninitialized constant Factorial (NameError)
This is normal because we don’t have a Factorial
class yet.
Let’s create one:
class Factorial end
Next error will be:
undefined method 'factorial_of'
I fix this by creating the factorial_of
method:
class Factorial def factorial_of end end
Then run the code again:
wrong number of arguments (given 1, expected 0)
Another error! But you know what?
That’s a good thing 🙂
Errors are not something to be frustrated with.
They are feedback.
Now:
Add one method argument to the factorial_of
method:
class Factorial def factorial_of(n) end end
What you get now is a test failure:
expected: 120 got: nil
This is exactly where you want to be at this point!
The next task is to implement the method:
class Factorial def factorial_of(n) (1..n).inject(:*) end end
And you’ll get your first passing test:
. Finished in 0.00315 seconds (files took 0.09083 seconds to load) 1 example, 0 failures
This is what we call test-driven development (TDD).
You write the tests first, then let the tests guide you on what you need to do next.
RSpec Let Method
If you want to write many tests & reuse the same objects you can define these objects with let
statements.
It looks like this:
describe Factorial do let(:calculator) { Factorial.new } it "finds the factorial of 5" do expect(calculator.factorial_of(5)).to eq(120) end end
Now you can reuse calculator
in all your tests under the same describe
block.
One thing you should know about let
is that it’s “lazy”.
What do I mean by that?
The object won’t be created until the first time you use it.
This can make a difference if creating this object has side-effects, like creating database entries, or writing to a file.
It would be best to avoid these side-effects, but if you can’t do that then use the let!
method.
Example:
let!(:user) { User.create("rspec@rubyguides.com") }
The let!
method is non-lazy, so the object will be created before any tests are run.
How to Use The Subject Method
Another version of let
is subject
.
The only difference is that you can only have one subject
, and it’s meant to be an instance of the main object you are testing.
RSpec already creates a default subject
like this:
subject { Factorial.new }
This is called the “implicit subject”.
You can use it like this:
describe Factorial do it "finds the factorial of 5" do expect(subject.factorial_of(5)).to eq(120) end end
You can give your subject a name:
subject(:calculator) { Factorial.new }
This behaves the same way as using let
, but it enables the use of one-line expectations:
it { should be_empty }
How to Run Code Before All Your Tests
RSpec has execution hooks you can use to run something before & after every test, or a whole group of tests.
For example:
describe Shop do before(:all) { Shop.prepare_database } after (:all) { Shop.cleanup_database } end
If you want to run this code for each example (example = test in RSpec) you can use :each
instead of :all
.
How To Create Testing Subgroups
If you’re testing different scenarios in your app then it may be helpful to group related tests together.
You can do this using a context block in RSpec.
Here’s an example:
describe Course do context "when user is logged in" do it "displays the course lessons" do end it "displays the course description" do end end context "when user it NOT logged in" do it "redirects to login page" do end it "it shows a message" do end end end
How to Temporarily Disable a Test
It’s possible to disable a test for debugging purposes.
All you have to do is to change it
to xit
for the tests you want to disable.
Example:
xit "eats lots of bacon" do end
Don’t forget to remove the x
when you are done!
Running Examples By Name
Instead of disabling tests, you can filter the tests you want to run with the -e
flag.
Example:
> ruby person.rb -e bacon
This filtering is based on the test name, so the above example will match any test with the word “bacon” on it.
RSpec Expectations & Matchers
You may remember this example we have been using:
expect(calculator.factorial_of(5)).to eq(120)
But what is this eq(120)
part?
Well, 120 is the value we are expecting…
…and eq
is what we call a matcher.
Matchers are how RSpec compares the output of your method with your expected value.
In the case of eq
, RSpec uses the ==
operator (read more about Ruby operators).
But there are other matchers you can use.
For example, the be_something
matcher:
expect(nil).to be_nil
Where something
is a predicate method (like empty?
) that is going to be called on the test results.
Other useful matchers:
- include (for arrays & hashes)
- start_with
- end_with
- match (for regular expression matching)
- be_between
- have_key / have_value (for hashes)
- be_instance_of
- respond_to
- have_attributes (for testing instance variables)
A matcher that needs special attention is the raise_error
matcher.
The reason for that is that to use it you have to wrap your expectation within a block.
Like this:
expect{ :x.count }.to raise_error(NoMethodError)
The change
matcher also works like this:
expect{ stock.increment }.to change(stock, :value).by(100)
RSpec Formatters
The default RSpec output is in the “progress” format.
With this format you see dots (.
) representing 1 passing test each, an F
for a failed test (expected & actual don’t match), or an E
for an error.
But there are alternative formatting options you can use.
Here’s a list:
- progress
- documentation
- json
- html
You can enable them with the -f flag:
> ruby factorial.rb -f d Person eats lots of healthy food writes many articles Finished in 0.00154 seconds (files took 0.09898 seconds to load) 2 examples, 0 failures
The documentation format uses your test descriptions to generate the output.
How to Find Slow Tests
RSpec comes with a very handy option to profile your tests.
Just by passing the --profile
flag you’ll be able to see how long each test takes to run & fix the really slow ones.
Here’s an example:
> ruby factorial.rb --profile Factorial finds the factorial of 5 0.00043 seconds
RSpec Video Tutorial
[responsive_video type=’youtube’ hide_related=’0′ hide_logo=’0′ hide_controls=’0′ hide_title=’1′ hide_fullscreen=’0′ autoplay=’0′]https://www.youtube.com/watch?v=LdutLs5-ObI[/responsive_video]
Summary
You have learned how to write tests using the RSpec testing framework.
Now it’s your turn to start writing your own test!
Very useful and didactic, thanks for sharing it.
I’m glad you found it useful! Thanks for reading 🙂
Thorough, useful, and an easy read; very nice!
Hey Alex, thanks for your comment! 🙂
Very helpful. Thanks very much!
Hey Thomas, thanks for reading! 🙂
Thank you very much!
Thanks for reading Olga 🙂
Perfect and in-depth 🙂
Thanks for reading 🙂
Hey, congratz for the article!
Just some typos:
At the end of the article you’ve added these examples:
ruby factorial.rb -f d
andruby factorial.rb --profile
.I think you would type
rspec factorial ...
isn’t?Thanks for your comment!
It’s not a typo because I’m using
rspec/autorun
🙂