---
title: How we test Rails applications
teaser: |
  We use RSpec feature and specs heavily,
  controller and view specs more judiciously,
  FactoryBot for test data,
  JavaScript integration specs with Poltergeist or Capybara Webkit,
  like test doubles and test spies but not test mocks,
  and we stub external requests with Webmock.
tags: web,rails,testing
author: Josh Steiner
published_on: 2014-01-14
---

I'm frequently asked what it takes to begin testing Rails applications. The
hardest part of being a beginner is that you often don't know the terminology or
what questions you should be asking. What follows is a high-level overview of
the tools we use, why we use them, and some tips to keep in mind as you are
starting out.

## RSpec

We use RSpec over Test::Unit because the syntax encourages human readable tests.
While you could spend days arguing over what testing framework to use, and they
all have their merits, the most important thing is that you are testing.

## Feature specs

Feature specs, a kind of acceptance test, are high-level tests that walk through
your entire application ensuring that each of the components work together.
They're written from the perspective of a user clicking around the application
and filling in forms. We use RSpec and [Capybara][capybara], which allow you to
write tests that can interact with the web page in this manner.

Here is an example RSpec feature test:

```ruby
# spec/features/user_creates_a_foobar_spec.rb

feature 'User creates a foobar' do
  scenario 'they see the foobar on the page' do
    visit new_foobar_path

    fill_in 'Name', with: 'My foobar'
    click_button 'Create Foobar'

    expect(page).to have_css '.foobar-name', 'My foobar'
  end
end
```

This test emulates a user visiting the new `foobar` form, filling it in, and
clicking "Create". The test then asserts that the page has the text of the
created `foobar` where it expects it to be.

While these are great for testing high level functionality, keep in mind that
feature specs are slow to run. Instead of testing every possible path through
your application with Capybara, leave testing edge cases up to your model, view,
and controller specs.

I tend to get questions about distinguishing between RSpec and Capybara methods.
Capybara methods are the ones that are actually interacting with the page, i.e.
clicks, form interaction, or finding elements on the page. Check out the docs
for more info on Capybara's [finders][capybara_finders],
[matchers][capybara_matchers], and [actions][capybara_actions].

## Model specs

Model specs are similar to unit tests in that they are used to test smaller
parts of the system, such as classes or methods. Sometimes they interact with
the database, too. They should be fast and handle edge cases for the [system
under test][system_under_test].

In RSpec, they look something like this:

```ruby
# spec/models/user_spec.rb

# Prefix class methods with a '.'
describe User, '.active' do
  it 'returns only active users' do
    # setup
    active_user = create(:user, active: true)
    non_active_user = create(:user, active: false)

    # exercise
    result = User.active

    # verify
    expect(result).to eq [active_user]

    # teardown is handled for you by RSpec
  end
end

# Prefix instance methods with a '#'
describe User, '#name' do
  it 'returns the concatenated first and last name' do
    # setup
    user = build(:user, first_name: 'Josh', last_name: 'Steiner')

    # excercise and verify
    expect(user.name).to eq 'Josh Steiner'
  end
end
```

To maintain readability, be sure you are writing [Four Phase Tests][four_phase_tests].

## Controller specs

When testing multiple paths through a controller is necessary, we favor using
controller specs over feature specs, as they are faster to run and often easier
to write.

A good use case is for testing authentication:

```ruby
# spec/controllers/sessions_controller_spec.rb

describe 'POST #create' do
  context 'when password is invalid' do
    it 'renders the page with error' do
      user = create(:user)

      post :create, session: { email: user.email, password: 'invalid' }

      expect(response).to render_template(:new)
      expect(flash[:notice]).to match(/^Email and password do not match/)
    end
  end

  context 'when password is valid' do
    it 'sets the user in the session and redirects them to their dashboard' do
      user = create(:user)

      post :create, session: { email: user.email, password: user.password }

      expect(response).to redirect_to '/dashboard'
      expect(controller.current_user).to eq user
    end
  end
end
```

## View specs

View specs are great for testing the conditional display of information in your
templates. A lot of developers forget about these tests and use feature specs
instead, then wonder why they have a long running test suite. While you can
cover each view conditional with a feature spec, I prefer to use view specs like
the following:

```ruby
# spec/views/products/_product.html.erb_spec.rb

describe 'products/_product.html.erb' do
  context 'when the product has a url' do
    it 'displays the url' do
      assign(:product, build(:product, url: 'http://example.com')

      render

      expect(rendered).to have_link 'Product', href: 'http://example.com'
    end
  end

  context 'when the product url is nil' do
    it "displays 'None'" do
      assign(:product, build(:product, url: nil)

      render

      expect(rendered).to have_content 'None'
    end
  end
end
```

## FactoryBot

While writing your tests you will need a way to set up database records in a way
to test against them in different scenarios. You could use the built-in
`User.create`, but that gets tedious when you have many validations on your
model. With `User.create` you have to specify attributes to fulfill the
validations, even if your test has nothing to do with those validations. On top
of that, if you ever change your validations later, you have to reflect those
changes across every test in your suite. The solution is to use either factories
or fixtures to create models.

We prefer factories (with [FactoryBot][factory_bot][^1]) over Rails fixtures,
because fixtures are a form of [Mystery Guest][mystery_guest]. Fixtures make it
hard to see cause and effect, because part of the logic is defined in a file far
away from the context in which you are using it. Because fixtures are
implemented so far away from your tests, they tend to be hard to control.

Factories, on the other hand, put the logic right in the test. They make it easy
to see what is happening at a glance and are more flexible to different
scenarios you may want to set up. While factories are slower than fixtures, we
think the benefits in flexibility and readability outweigh the costs.

Persisting to the database slows down tests. Whenever possible, [favor using
FactoryBot's `build_stubbed` over `create`][build_stubbed]. `build_stubbed`
will generate the object in memory and save you from having to write to the
disk. If you are testing something in which you have to query for the object
(like `User.where(admin: true)`), your database will be expecting to find it in
the database, meaning you must use `create`.

## Running specs with JavaScript

You will eventually run into a scenario where you need to test some
functionality that depends on a piece of JavaScript. Running your specs with the
default driver will not run any JavaScript on the page.

You need two things to run a feature spec with JavaScript.

1. Install a JavaScript driver

   There are two types of JavaScript drivers. Something like Selenium will open
   a GUI browser and click around your page while you watch it. This can be a
   useful tool to visualize while debugging. Unfortunately, booting up an
   entire GUI browser is slow. For this reason, we prefer using a headless
   browser. For Rails, you will want to use either [Poltergeist][poltergeist]
   or [Capybara Webkit][capybara_webkit].

1. Tell the specific test to run with the JavaScript metadata key

   ```ruby
   feature 'User creates a foobar' do
     scenario 'they see the foobar on the page', js: true do
       ...
     end
   end
   ```

With the above in place, RSpec will run any JavaScript necessary.

## Database Cleaner

When running your tests by default, Rails wraps each scenario in a database
transaction. This means, at the end of each test, Rails will rollback any
changes to the database that happened within that spec. This is a good thing, as
we don't want any of our tests having side effects on other tests.

Unfortunately, when we use a JavaScript driver, the test is run in another
thread. This means it does not share a connection to the database and your test
will have to commit the transactions in order for the running application to see
the data. To get around this, we can allow the database to commit the data and
subsequently truncate the database after each spec. This is slower than
transactions, however, so we want to use truncation only when necessary.

This is where [Database Cleaner][database_cleaner] comes in. Database Cleaner
allows you to configure when each strategy is used. I recommend reading [Avdi's
post][database_cleaner_intro] for all the gory details. It's a pretty painless
setup, and I typically copy [this file][database_cleaner_file] from project to
project, or use [Suspenders][suspenders] so that it's set up out of the box.

## Test doubles and stubs

Test doubles are simple objects that emulate another object in your system.
Often, you will want a simpler stand-in and only need to test one attribute, so
it is not worth loading an entire `ActiveRecord` object.

```ruby
car = double(:car)
```

When you use stubs, you are telling an object to respond to a given method in a
known way. If we stub our double from before

```ruby
car.stub(:max_speed).and_return(120)
```

We can now expect our `car` object to always return `120` when prompted for its
`max_speed`. This is a great way to get an impromptu object that responds to a
method without having to use a real object in your system that brings its
dependencies with it. In this example, we stubbed a method on a double, but you
can stub virtually any method on any object.

We can simplify this into one line:

```ruby
car = double(:car, max_speed: 120)
```

## Test spies

While testing your application, you are going to run into scenarios where you
want to validate that an object receives a specific method. In order to follow
[Four Phase Test][four_phase_tests] best practices, [we use test
spies][test_spies] so that our expectations fall into the `verify` stage of the
test. Previously we used [Bourne][bourne] for this, but RSpec now includes this
functionality in [RSpec Mocks][rspec_mocks]. Here's an example from the docs:

```ruby
invitation = double('invitation', accept: true)

user.accept_invitation(invitation)

expect(invitation).to have_received(:accept)
```

## Stubbing external requests with Webmock

Test suites that rely on third party services are slow, fail without an internet
connection, and may have trouble with the services' rate limits or lack of a
sandbox environment.

Ensure that your test suite does not interact with third party services by
stubbing out external HTTP requests with [Webmock][webmock]. This can be
configured in `spec/spec_helper.rb`:

```ruby
require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true)
```

Instead of making third party requests, [learn how to stub external services in
tests][stub_external_services].

## Testing Rails book

This was just an overview of how we test Rails applications. Check out our new
book, [Testing Rails][testing_rails_book], to learn more. The book covers each
type of test in depth, intermediate testing concepts, common anti-patterns
that trip up even intermediate developers, and it includes an example app.

[capybara]: https://github.com/jnicklas/capybara
[capybara_actions]: http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions
[capybara_finders]: http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders
[capybara_matchers]: http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Matchers
[system_under_test]: http://xunitpatterns.com/SUT.html
[four_phase_tests]: https://thoughtbot.com/blog/four-phase-test
[factory_bot]: https://github.com/thoughtbot/factory_bot
[mystery_guest]: https://thoughtbot.com/blog/post/159805321/mystery-guest
[build_stubbed]: https://thoughtbot.com/blog/use-factory-bots-build-stubbed-for-a-faster-test
[poltergeist]: https://github.com/jonleighton/poltergeist
[capybara_webkit]: https://github.com/thoughtbot/capybara-webkit
[database_cleaner]: https://github.com/bmabey/database_cleaner
[database_cleaner_intro]: http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/
[database_cleaner_file]: https://gist.github.com/jsteiner/8362013
[suspenders]: https://github.com/thoughtbot/suspenders
[test_spies]: https://thoughtbot.com/blog/spy-vs-spy
[webmock]: https://github.com/bblimke/webmock#real-requests-to-network-can-be-allowed-or-disabled
[stub_external_services]: https://thoughtbot.com/blog/how-to-stub-external-services-in-tests
[rspec_mocks]: https://github.com/rspec/rspec-mocks
[bourne]: https://github.com/thoughtbot/bourne
[testing_rails_book]: http://testingrailsbook.com

[^1]:
    Looking for FactoryGirl? The library was renamed in 2017.
    [Project name history can be found here.](https://github.com/thoughtbot/factory_bot/blob/master/NAME.md)

## Want to learn more about testing?

Check out all the ways thoughtbot can help your team and [tell us about your project.](https://thoughtbot.com/services/ruby-on-rails-development)
