How we made our Rails test suite about 75% faster
Tags: rails, performance, testing
Published: 2021-05-08 13:42:18

---
title: How we made our Rails test suite about 75% faster
published: true
date: 2020-03-08 09:24:00 UTC
tags: rails, performance, testing
canonical_url: https://ramonh.dev/rails/capybara/testing/2020/03/08/speeding-up-rails-tests/
---

One of my recent tasks has been to take the current test suite for a large-ish Rails app and speed it up as much as possible. This is the story of how I managed to do so without compromising the integrity of the tests.

<!-- more -->
## Our current suite

At the time of starting, our test suite contained 1347 assertions, and took approximately 12 minutes to run on a 2018 Macbook Pro.

The setup is as follows:

- The testing framework is [`MiniTest`](https://guides.rubyonrails.org/testing.html), which comes set up by default in new Rails applications.
- The database is populated using thoughtbot’s [`factory_bot`](https://github.com/thoughtbot/factory_bot).
- Feature tests are done using [`minitest-rails-capybara`](https://github.com/blowmage/minitest-rails-capybara).
- Browser driver used for Capybara is [`geckodriver`](https://github.com/mozilla/geckodriver).

## Timing of tests

I was able to get a breakdown for how long each test took by running the following command:

```
bundle exec rake test TESTOPTS="-v"

```

The output then looked as the following example:

```
Displaying orders for today::Given the company does not allows soup orders#test_Soup_orders_being_hidden = 1.02 s = .

```

With this, I was able to figure out which tests were taking longer than others.

## Watching feature tests

Normally, our tests are run using headless Firefox, as mentioned above, with`geckodriver`. However, I was able to run the tests with an attached window and monitor which steps were taking particularly long.

## Monitoring the database

I entered the following line into `config/environments/test.rb`:

```
Rails.logger.level = Logger::DEBUG

```

I then could watch SQL commands during my tests with the following:

```
RAILS_ENABLE_TEST_LOG=1 bundle exec rails test

```

## Speeding up Capybara tests

Our biggest bottleneck when starting out was the feature tests. I was able to fix a lot of these by looking deeper into the [Capybara documentation](https://www.rubydoc.info/github/jnicklas/capybara).

For example, when checking that a piece of text wasn’t visible, I’d run the following:

```
refute page.has_content?(text)

```

Which does work! It turns out, however, that this was causing Capybara to look for the text on the page until its timeout, and then return `false`. When having lots of these calls in a test, it adds up quickly.

A much faster way is to do the following:

```
assert page.has_no_content?(text)

```

There are lots of examples for this, such as `#has_no_field?`, `#has_no_select?`, and even `#has_no_selector?`.

Going through our tests and replacing these checks sped things up greatly!

## Replacing (some) factories with fixtures

Don’t get me wrong, `FactoryBot` is an incredibly helpful gem. The problem was with the way I was using it!

See, a lot, if not all, of the tests were using a similar database setup in terms of data.

What I was doing previously was using `FactoryBot` to create a series of sample data used by tests.

This created a huge overload in terms of setting up the database. There are things that can be done, such as using [transactional fixtures](https://guides.rubyonrails.org/testing.html) to dump the database between tests, but setting up the stage still takes a lot of time.

I then re-took a look at [Rails fixtures](https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures). With these, I can set up a series of sample data that can be loaded for every test.

What I **didn’t know** , though, is that using fixtures significantly lowers the number of SQL `INSERT` statements, as opposed to a single one for each factory, reducing the amount of time needed to set the stage!

## All’s well that keeps going

These are the two biggest factors that contributed to a faster suite. Got it down from 12 minutes to 3!

This doesn’t by any means indicate that I’m done, but definitely a great set of steps forward.

There are certainly other paths I could take to speed up our test suite, such as:

- Re-considering the exhaustive nature of certain feature tests. Do I really need to check certain aspects in such detail?
- [Parallel testing](https://guides.rubyonrails.org/testing.html#parallel-testing). We’re not on Rails 6 yet but would be nice to have!

Yo, do you have any favourite techniques to make your Rails tests faster? [Do please let me know!](https://twitter.com/hola_soy_milk)