Rails Testing Setup

How do you setup a Rails project for testing? In this article we will explore what a testing suite, using RSpec and other tools, for a robust developers experience. This article touches on my approaches to testing tools as I have been focused on learning RSpec.

TLTR: Hey, just go grab the code

Basic project setup.

First we need to start a rails project. We will use the -T switch to exclude testing since we will opt for RSpec over the Rails default of minitest. We will also default to Postgres for our database. This is not required, just my preference:

rails new rails_testing_setup -T --database=postgresql

Create and migrate the databases.

rails db:prepare && rails db:migrate

You can start the rails server and see the familiar Rails splash screen.

Basic Testing

To set up testing we are going to use RSpec, which according to the RSpec web site: "Behaviour Driven Development for Ruby. Making TDD Productive and Fun." I am not sure about all of that, but it is what we are setting u0p.

Basic Rspec setup

We will add two gems to your Gemfile in the development and test group, RSpec and Capybara, which helps you test web applications by simulating how a real user would interact with your app.:

group :development, :test do
  ...
  gem "capybara", ">= 2.15"
  gem "rspec-rails"
  ...
end

If by chance, you forgot to exclude the testing framework on the rails setup, make sure you delete the test directory. It doesn't hurt anything, but it is a best practice not to include two testing directories. Then setup RSpec: rails g rspec:install. This will install a spec directory with a couple of helper files.

Now, we need to clean up and add some configuration to the rails_helper.rb file. I start by cleaning out all the comments, but that is just me. Then update the RSpec.configuration block:

RSpec.configure do |config|
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end

also add a require at the top of the file below rails for capybara:

require "rspec/rails"
require "capybara/rails"

If you run rspec everything should be green and ok, since we have no test.

Database Cleaner I also want to make sure the test database is a clean slate, so we need to add the gem database_cleaner to the development and test group, which should now look so:

group :development, :test do
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  gem "capybara", ">= 2.15"
  gem 'database_cleaner'
  gem 'rspec-rails'
end

Run bundle install and then configure our rails_helper.rb again:

RSpec.configure do |config|
  config.use_transactional_fixtures = true
  config.before(:suite) { DatabaseCleaner.clean_with(:truncation) }
  config.before(:each) { DatabaseCleaner.strategy = :transaction }
  config.before(:each, js: true) { DatabaseCleaner.strategy = :truncation }
  config.before(:each) { DatabaseCleaner.start }
  config.before(:each) { DatabaseCleaner.clean }
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end

If you run rspec everything should be green and ok, since we have no test.

Factories

So, factories are a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.

We will install a Rails version of FactoryBot, a replacement for FactoryGirl. So, we need to add the gem factory_bot_rails to the development and test group, which should now look so:

group :development, :test do
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  gem 'database_cleaner'
  gem "factory_bot_rails", git: "http://github.com/thoughtbot/factory_bot_rails"
  gem 'rspec-rails'
end

Run bundle install. If you run rspec everything should be green and ok, since we have no test.

Add a test

Let's add a test. In true Test Driven development, we are going to create a test to check for a root path that renders a home page. We are going to create a feature test. RSpec provides a whole series of generators and we will use the feature generator, which will create the features directory and the associated test we name.

rails g rspec:feature static

We will create the following feature test, which will simulate a user visiting a static page at /. Thankfully this is available because of capybara. Edit static_spec.rb:

require 'rails_helper'

RSpec.feature 'Statics', type: :feature do
  scenario do
    visit "/"

    expect(page.status_code).to eq(200)
  end
end

This test will fail because we need a controller for a home page. Let's create a static controller that we can use for other static pages in the project. By stipulating home it will generate a controller method and a view of home.

rails g controller static home

Unfortunately the test will still fail because we do not have a root route specified in config/routes.rb. Add the following:

root 'static#home'

Now our test are GREEN and pass.

Automated Testing

So far, we have a perfectly acceptable test environment. However, I would much rather automate the testing so we do not have to run the rspec command.

Guard We will use a gem called guard-rspec, which automatically will run your spec files. Let's add a few gem files to the development group:

group :development do
  gem 'guard'
  gem 'guard-rspec'
  gem 'listen', '~> 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'web-console', '>= 3.3.0'
end

We need to initialize Guard, which will create a configuration file: bundle exec guard init. I need to make one change to the newly created Guardfile. I have libnotify configured on my Linux system and everytime guard runs the test suite I get pop-up system notifications. You might like these for other system tasks (i.e. updates, email notifications, etc) but they make me crazy to see these pop-up notifications everytime I edit a spec file. So I add the following to the end of the Guardfile to stop these notifications for Guard: notification: off.

Progress reporter

One other feature I like. While learning minitest I loved having a more pronounced progress bar that was provided by minitest_reporter. We can enjoy this same feature with a gem called fuubar.

Let's add the gem to the development group:

group :development do
  bem 'fuubar'
  gem 'guard'
  gem 'guard-rspec'
  gem 'listen', '~> 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'web-console', '>= 3.3.0'
end

We need to add require 'fuubar' to the top of the rails_helper.rb file, and add to the configuration block:

RSpec.configure do |config|
  config.use_transactional_fixtures = true
  config.before(:suite) { DatabaseCleaner.clean_with(:truncation) }
  config.before(:each) { DatabaseCleaner.strategy = :transaction }
  config.before(:each, js: true) { DatabaseCleaner.strategy = :truncation }
  config.before(:each) { DatabaseCleaner.start }
  config.before(:each) { DatabaseCleaner.clean }
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
  config.fuubar_progress_bar_options = { format: 'Completed Tests <%B> %p%% %a' }
end

The progress bar options can be configured to your likings.

Edit the .rspec file:

--require spec_helper
--format Fuubar
--color

So how does it work?

Notice that we have one test pass, and the progress bar is noted as Completed Test which we defined in the fuubar configuration.

Code Coverage

Next I want to add code coverage with a gem called simplecov. According to thesimplecov README:

SimpleCov is a code coverage analysis tool for Ruby. It uses Ruby's built-in Coverage library to gather code coverage data, but makes processing its results much easier by providing a clean API to filter, group, merge, format, and display those results, giving you a complete code coverage suite that can be set up with just a couple lines of code. SimpleCov/Coverage track covered ruby code, gathering coverage for common templating solutions like erb, slim and haml is not supported.

The setup is super easy. In our Gemfile, in the test group we add a new gem:

group :test do
  gem 'selenium-webdriver'
  gem 'simplecov', require: false
  gem 'webdrivers'
end

Now add the following to the VERY top of the spec_helper.rb file:

require 'simplecov'
SimpleCov.start

We need to add a filter, so simplecov skips rails_helper.rb. Create: .simplecov and add the following:

require 'simplecov'

SimpleCov.start do
  add_filter 'spec/rails_helper.rb'
end

Notice we have the path to a coverage report stored in a new coverage directory in the root of our project path. This way we can analyse our code coverage. You can open in your web browser.

Code style

Lastly, I want to install rubocop to manage our code presentation quality. We will install three gems and add them to our development group:

group :development do
  ...
  gem "rubocop"
  gem "rubocop-rails", require: false
  gem "rubocop-rspec"
  ...
end

Using extensions in your IDE/editor we can get recommendations for a consistent code quality by using Rubocop. We can create a configuration file for style overrides as you see fit. Below is my short .rubocop.yml configuration:

require:
  - rubocop-rails
  - rubocop-rspec

AllCops:
  Exclude:
    - bin/**/*
    - config/**/*
    - db/schema.rb
    - db/migrate/*.rb
    - node_modules/**/*
    - tmp/**/*
    - vendor/**/*

#################### Bundler ###########################

Bundler/OrderedGems:
  Description: >-
    Gems within groups in the Gemfile should be alphabetically sorted.
  Enabled: true
  ConsiderPunctuation: true

#################### Layout ###########################

Layout/LineLength:
  Description: 'Checks that line length does not exceed the configured limit.'
  AutoCorrect: true # this is false by default
  Exclude:
    - Gemfile

#################### Style ###############################

Style/StringLiterals:
  Description: 'Checks if uses of quotes match the configured preference.'
  StyleGuide: '#consistent-string-literals'
  Enabled: true
  EnforcedStyle: double_quotes
  ConsistentQuotesInMultiline: true

#################### RSpec cops from rubocop-rspec #######
# https://github.com/rubocop-hq/rubocop-rspec/blob/master/config/default.yml

RSpec/DescribeClass:
  Description: Check that the first argument to the top level describe is a constant.
  Enabled: true
  StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass
  Exclude:
    - 'spec/requests/**/*'
    - 'spec/system/**/*'
    - 'spec/tasks/**/*'

RSpec/ExampleLength:
  Description: Checks for long examples.
  Enabled: true
  Max: 15
  StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleLength

RSpec/MultipleExpectations:
  Description: Checks if examples contain too many `expect` calls.
  Enabled: true
  Max: 8
  StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations

This is the testing framework setup I have started using on all my side projects. I hope this helps someone in some way.

Footnote

This has been fun. Leave a comment or send me a DM on Twitter.

Shameless Plug: If you work at a great company and you are in the market for a Software Developer with a varied skill set and life experiences, send me a message on Twitter and check out my LinkedIn.