Beginners Guide To Rails Testing
Beginners Guide To Rails Testing
BEGINNER'S
GUIDE TO
RAILS
TESTING
JASON SWETT
host of the
Rails with Jason Podcast
2
The Beginner’s Guide to Rails Testing
Jason Swett
ii
Contents
Introduction v
1 What are the different kinds of Rails tests and when should I use
each? 1
1.1 The eight types of RSpec specs . . . . . . . . . . . . . . . . . 1
1.2 Spec types I always use . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 System specs . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 Model specs . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Spec types I rarely use . . . . . . . . . . . . . . . . . . . . . 4
1.3.1 Request specs . . . . . . . . . . . . . . . . . . . . . . 4
1.3.2 Helper specs . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Spec types I never use . . . . . . . . . . . . . . . . . . . . . . 5
1.4.1 View specs and routing specs . . . . . . . . . . . . . 5
1.4.2 Mailer specs and job specs . . . . . . . . . . . . . . . 5
1.5 Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 What are all the Rails testing tools and how do I use them? 7
2.1 RSpec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Factory Bot . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.1 Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 Factories . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.3 Relative merits of fixtures and factories . . . . . . . . 8
2.3 Capybara . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4 VCR and WebMock . . . . . . . . . . . . . . . . . . . . . . . 9
iii
iv CONTENTS
2.5 Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
6.3.2 factory_bot_rails . . . . . . . . . . . . . . . . . . . . 26
6.3.3 capybara . . . . . . . . . . . . . . . . . . . . . . . . 26
6.3.4 webdrivers . . . . . . . . . . . . . . . . . . . . . . . 26
6.3.5 faker . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6.3.6 Honorable mention . . . . . . . . . . . . . . . . . . . 27
6.4 Next steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
vii
viii INTRODUCTION
Chapter 1
• Model specs
1
2CHAPTER 1. WHAT ARE THE DIFFERENT KINDS OF RAILS TESTS AND WHEN SHOU
• Helper specs
• View specs
• Routing specs
• Mailer specs
• Job specs
There are two lines with asterisks. These are cases where the RSpec team
decreed one spec type obsolete and replaced it with a new type. I’m only in-
cluding those ones for completeness.
So the up-to-date list is really the following.
• Model specs
• System specs
• Request specs
• Helper specs
• View specs
• Routing specs
• Mailer specs
• Job specs
Let’s talk about each of these spec types in detail. I’ll explain why I use the
ones I use and why I ignore the ones I ignore.
expensive to use a system spec in a certain case then I’ll use a request spec
instead. I write more about my reasoning here.
1.5 Takeaways
RSpec offers a lot of different spec types but you can typically meet 98% of
your needs with just system specs and model specs.
If you’re a total beginner, I’d suggest starting with system specs.
6CHAPTER 1. WHAT ARE THE DIFFERENT KINDS OF RAILS TESTS AND WHEN SHOU
Chapter 2
2.1 RSpec
RSpec is a test framework. A test framework is what gives us a structure for
writing our tests as well as the ability to run our tests.
There are other test frameworks but RSpec is the most popular one for com-
mercial Rails projects. The second most popular test framework is Minitest.
Test frameworks differ syntactically but the testing principles and practices
are going to be pretty much the same no matter what framework you’re using.
(If you’re not sure whether you should learn RSpec or Minitest, I write about
that here.)
7
8CHAPTER 2. WHAT ARE ALL THE RAILS TESTING TOOLS AND HOW DO I USE THEM
2.2.1 Fixtures
Fixtures typically take the form of one or more YAML files with some hard-
coded data. The data is translated into database records one time, before any of
the tests are run, and then deleted afterward. (This happens in a separate test
database instance of course.)
2.2.2 Factories
With factories, database data is generated specifically for each test. Instead
of loading all the data once at the beginning and deleting it at the end, data is
inserted before each test case and then deleted before the next test case starts.
(More precisely, the data isn’t deleted, but rather the test is run inside a database
transaction and the data is never committed in the first place, but that’s a me-
chanical detail that’s not important right now.)
2.3 Capybara
Some Rails tests only exercise Ruby code. Other tests actually open up a
browser and simulate user clicks and keystrokes.
Simulating user input this way requires us to use some sort of tool to ma-
nipulate the browser. Capybara is a library that uses Ruby to wrap a driver
(usually the Selenium driver), letting us simulate clicks and keystrokes using
convenient Ruby methods.
For more examples of how to use Capybara, go here.
2.5 Takeaways
Rails testing tools take some time to learn, but the important part (and perhaps
more difficult part) is learning testing principles.
10CHAPTER 2. WHAT ARE ALL THE RAILS TESTING TOOLS AND HOW DO I USE THEM
If you’re just getting started with Rails testing, the next step I would suggest
is to learn about the different types of Rails tests and when to use them.
Chapter 3
11
12CHAPTER 3. WHICH TEST FRAMEWORK SHOULD I LEARN, RSPEC OR MINITEST?
Even if my numbers are off by quite a bit, RSpec is still the more popular
framework.
This argument is flawed though. We’re free to make our own choices on
the big things but we can’t dictate what comes along with those choices. We
can choose to use Rails instead of a different framework, but we can’t reason-
ably say that we’re only going to work on Rails projects that use, for example,
Minitest and MySQL and Angular and no other combination of technologies.
We have to compromise a little or face extremely limited job options.
4.1 Laziness
It might sound funny to name laziness as the first motivation for writing tests ha-
bitually. After all, tests seem like extra work. Writing tests consistently seems
15
16CHAPTER 4. HOW DO I MAKE TESTING A HABITUAL PART OF MY DEVELOPMENT
like something that would require discipline. But for me and many of the people
who responded to my poll, it’s quite the opposite.
4.2 Fear
Fear is another powerful impetus for testing. If I don’t write tests for my fea-
tures, it increases the risk that I release a bug to production. Bugs cause me
shame and embarrassment. I don’t want to feel embarrassment or shame.
Bugs may also have negative business consequences to the company I work
for. This could negatively affect the company’s ability or willingness to pay me
as much as I want.
When laziness doesn’t drive me to write tests, fear often does.
18CHAPTER 4. HOW DO I MAKE TESTING A HABITUAL PART OF MY DEVELOPMENT
4.3 Pride
Lastly there’s pride. (I find Larry Wall’s “hubris” a little too strong a word.)
Sometimes, when I’m tempted not to write a test for a feature, I imagine
another developer stumbling across my work in the future and seeing that there
are no tests. I imagine myself sheepishly admitting to that developer that I didn’t
bother to write tests for that feature. Why didn’t I write tests? No good reason.
As the arrogant person that I am, this imaginary interaction brings me pain.
I really don’t like the idea that somebody else would like at my work and make
a (legitimate) negative judgment.
I also want my work to be exemplary. If we hire a junior developer where
I work, I want to be able to point to my code and say “This is how we do it.” I
don’t know how I would explain that my test coverage is poor but I want theirs
to be good.
4.4 Takeaways
I’m not driven to write tests out of discipline. I also don’t consider testing to be
“extra” effort but rather an effort-saver.
The main forces that drive me to write tests are laziness, fear and pride.
Mostly laziness.
Chapter 5
“What level of test coverage should I shoot for?” is one of the questions most
commonly asked by beginners to Rails testing.
My answer is that you shouldn’t shoot for a particular level of test coverage.
I recommend that instead you make testing a habitual part of your development
workflow. A healthy level of test coverage will flow from there.
I also want to address why people ask this question. I think people ask this
because they want some way of knowing whether they’re testing “enough” or
doing testing “right”. Test coverage is one way of measuring this but I think
there are better, more meaningful ways.
I think that if you’re feeling the kinds of pains that missing tests leave in
their absence, then you need more tests. If you’re not feeling those kinds of
pains, then you’re good.
19
20CHAPTER 5. WHAT LEVEL OF TEST COVERAGE SHOULD I SHOOT FOR?
With the presence of a good test suite, deployments can happen many times
a day instead of just once every few weeks or months.
they may well seek somewhere else to work where the development experience
is more pleasant.
1. An application template that can add all the necessary gems and config-
uration
23
24CHAPTER 6. HOW DO I SET UP A NEW RAILS PROJECT FOR TESTING?
not to generate certain types of files. A more detailed explanation can be found
below the code.
The first chunk of code will add a certain set of gems to my Gemfile. A
more detailed explanation of these gems is below.
The second chunk of code creates a file at config/initializers/generators.rb.
The code in the file says “when a scaffold is generated, don’t generate files for
fixtures, view specs, helper specs, routing specs, request specs or controller
specs”. There are certain kinds of tests I tend not to write and I don’t want to
clutter up my codebase with a bunch of empty files. That’s not to say I never
write any of these types of tests, just sufficiently rarely that it makes more sense
for me to create files manually in those cases than for me to allow files to get
generated every single time I generate a scaffold.
Also, incidentally, I always use PostgreSQL. This choice of course has little
to do with testing but I’m including it for completeness.
In this particular case I’m also using the -m flag so I can pass in my appli-
cation template. Application templates can be specified using either a local file
path or a URL. In this case I’m using a URL so that you can just copy and paste
my full rails new command as-is if you want to.
$ git add .
$ git commit -a -m'Initial commit'
$ rails g rspec:install
6.3.1 rspec-rails
RSpec is one of the two most popular test frameworks for Rails, the other being
Minitest.
The rspec-rails gem is the version of the RSpec gem that’s specifically
fitted to Rails.
26CHAPTER 6. HOW DO I SET UP A NEW RAILS PROJECT FOR TESTING?
6.3.2 factory_bot_rails
Factory Bot is a tool for generating test data. Most Rails projects that use RSpec
also use Factory Bot.
Like rspec-rails, factory_bot_rails is a Rails-specific version of
a more general gem, factory_bot.
6.3.3 capybara
Capybara is a tool for writing acceptance tests, i.e. tests that interact with the
browser and simulate clicks and keystrokes.
The underlying tool that allows us to simulate user input in the browser is
called Selenium. Capybara allows us to control Selenium using Ruby.
6.3.4 webdrivers
In order for Selenium to work with a browser, Selenium needs drivers. There
are drivers for Chrome, drivers for Edge, etc. Unfortunately it can be somewhat
tedious to keep the drivers up to date. The webdrivers gem helps with this.
6.3.5 faker
By default, Factory Bot (the tool for generating test data) will give us factories
that look something like this:
FactoryBot.define do
factory :customer do
first_name { "MyString" }
last_name { "MyString" }
email { "MyString" }
end
end
This is fine for just one record but becomes a problem if we have multiple
records plus a unique constraint. If in this example we require each customer to
6.4. NEXT STEPS 27
have a unique email address, then we’ll get a database error when we create two
customer records because the email address of MyString will be a duplicate.
One possible solution to this problem is to replace the instances of "MyString"
with something like SecureRandom.hex. I don’t like this, though, because I
often find it helpful if my test values resemble the kinds of values they’re stand-
ing in for. With Faker, I can do something like this:
FactoryBot.define do
factory :customer do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
end
end
This can make test problems easier to troubleshoot than when test values
are simply random strings like c1f83cef2d1f74f77b88c9740cfb3c1e.
If you’re brand new to Rails testing and would like to see an example of
how I would actually write a test once I have the above application set up, I
might recommend my Rails testing “hello world” post.
Chapter 7
29
30CHAPTER 7. HOW DO I ADD TESTS TO AN EXISTING RAILS PROJECT?
the existing app, switch back to the throwaway app so you can strengthen your
skills more. Continue switching back and forth until you don’t need to anymore.
portant features: the setup and dependencies make adding tests difficult, some-
times prohibitively so.
What I would do instead is start with what’s easiest. I would look for the
simplest CRUD interfaces in the app and add some tests there, even if those
particular tests didn’t seem to add much value. The idea isn’t to add valuable
tests right from the start but to establish a beachhead that can be expanded upon.
7.2.3 Expand
Once you have a handful of tests for trivial features, you can add tests for in-
creasingly complicated features. This will give you a much better shot at ending
up with good test coverage than trying to start with the most valuable features
or trying to add tests for all new changes.
First of all, at the risk of stating the obvious, testing and TDD aren’t the same
thing. TDD is a specific kind of testing practice where you write the tests before
you write the code that makes the test pass. (If you want to go deeper into TDD,
I highly recommend Kent Beck’s Test Driven Development: By Example.)
33
34 CHAPTER 8. SHOULD I BE DOING TEST-DRIVEN DEVELOPMENT?
8.5 Takeaways
• Testing and test-driven development aren’t the same thing.