Tuesday, October 18, 2016

Setting up a Continuous Rails 5 TDD Feedback Loop with RSpec

RailsTDD

As part of a red-green-refactor cycle, I appreciate a smooth, fast feedback loop. The slower the loop; the more my attention gets to another shiny object and my concentration gets broken. This tutorial aims to help you achieve a smooth red-green-refactor loop.

By the end you should be able to:

  • Setup testing frameworks:
    • RSpec, Shoulda-Matchers, Database Cleaner, Capybara, Factory_girl_rails, Faker
  • Write a Model test
  • Improve TDD Feedback loop using Guard and Spring
  • Setup Notifications

Setup App

rails new exampleapp -T (-T does not generate test files)

Setup Testing Frameworks

Update the Gemfile Include the following gems in your Gemfile:

group :development, :test do
  gem 'rspec-rails', '~> 3.4'
  gem 'factory_girl_rails', '~> 4.5'
  gem 'capybara', '~> 2.5'
end
 
group :test do
  gem 'shoulda-matchers', '~> 3.0', require: false
  gem 'database_cleaner', '~> 1.5'
  gem 'faker', '~> 1.6.1'
end

Run bundle install

Note: If you have any problems installing gems, primarily NokoGiri, run the following commands:

xcode-select --install
bundle install

Setup RSpec

rails generate rspec:install

The install will generate a spec directory and some skeleton files, including a .rspec file, which will be used later.

Shoulda-Matchers

Shoulda-Matchers provide one line matchers for RSpec. We need to configure shoulda-matchers to work with RSpec by modifying the spec/rails_helper.rb file to include the following code snippet:

require 'rspec/rails'
 
require 'shoulda/matchers'
 
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

Setup Database Cleaner

To setup database_cleaner, make the following modifications to spec/rails_helper.rb. Once complete, database_cleaner will clean the database between each unit test and each test suite.

config.use_transactional_fixtures = false

Make a new directory called support inside of your spec directory:

mkdir spec/support

Inside the new support directory, create a new file called database_cleaner.rb and paste in the following:

RSpec.configure do |config|
 
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end
 
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end
 
  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end
 
  config.before(:each) do
    DatabaseCleaner.start
  end
 
  config.after(:each) do
    DatabaseCleaner.clean
  end
end

Allow Rails to run this support file in it's setup. In rails_helper.rb, un-comment the following line:

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

Setup Capybara

Since we've already got the Capybara gem installed, just add the following line to the top of spec_helper.rb file:

require 'capybara/rspec'

Setup Faker and Factory Girl

Faker gem generates random data for testing. We will use this with factory_girl_rails in making a factory. To integrate FactoryGirl with RSpec, add the following line in rails_helper.rb within the RSpec.configure block:

config.include FactoryGirl::Syntax::Methods

Create a factory

Create a directory named factories in your spec folder. In the factories directory, create a file named after your model. Ex. ideas.rb

***spec/factories/ideas.rb***
 
FactoryGirl.define do
  factory :idea do
    title       { Faker::Lorem.words(4) }
    description { Faker::Hacker.say_something_smart }
  end
end

This factory allows you to generate fake data based on data generated by Faker to be used by FactoryGirl in running our tests.

Let's write a test!

Now that we have our testing frameworks in place, let's write our first Model spec!

Create a file in your model/spec folder for your model:

***spec/models/idea_spec.rb***
 
require 'rails_helper'
 
RSpec.describe Idea, type: :model do
  it "has a valid factory" do
    idea = build(:idea)
    expect(idea).to be_valid
  end
end

Let's run the test from the terminal:

rspec spec/models/idea_spec.rb

This test will fail because we don't have an idea model. Let's create it!

rails g model Idea title:string description:string

This will want to overwrite the idea_spec.rb file and the spec/factories/ideas.rb file. Don't allow it, just enter 'n' for both. Then run:

rails db:migrate

Once the database has been migrated, lets run our test again.

rspec spec/models/idea_spec.rb

Green!

That feedback was good but it was a bit slow. Let's speed it up and gain more convenient feedback.

Improve TDD Feedback loop

Add the the following gems to your Gemfile:

group :development do
  gem 'guard-rspec', require: false
  gem 'spring-commands-rspec'
end
bundle install

Guard Setup

Guard is a neat gem that works with RSpec that watches your file system for changes and runs your test suite automatically. We need to initialize it, then we can run it

bundle exec guard init rspec
bundle exec guard

Spring Setup

Spring is new to Rails 5. This will setup our configuration once and then run tests repeatedly without having all the extra configuration overhead between each run. If you change rails configuration, you will need to restart the Spring.

For just one single test, this shaved a solid 5 seconds off of each test run!

spring binstub --all

Now, let's notify Guard to use Spring when running specs. In the Guardfile, modify the following line from:

guard :rspec, cmd: "bundle exec rspec" do

to:

guard :rspec, cmd: "bin/rspec", all_on_start: true do

Then run guard:

bundle exec guard

There should be a line in the output that says "Running via Spring preloader in process..." From your idea_spec.rb file, trigger a save. You'll see in the terminal that it will automatically run the tests and not have to preload rails.

Ah, much faster feedback but keeping the terminal window open is kind of a drag. Let's fix that!

Notifications

Let's provide notification pop ups whenever tests are run. This will allow us to keep the terminal window in the background and not have to even look at it unless we have test failures! Keeping our focus in the code requires less context switching and it's easy to setup!

I'm going to assume that you have a Mac and have brew installed. Install terminal-notifier

brew install terminal-notifier

Then, in your Gemfile, add the following gem:

group :development do
  gem 'terminal-notifier-guard', '~> 1.6.1'
end
bundle install

The last step is integrate terminal_notifier with guard. In your Guardfile, add the following line at the end of the file:

notification :terminal_notifier

Restart Guard if you had it running. After Guard runs your tests, you should receive a notification in the upper right hand corner saying all your tests passed!

Congrats! This TDD flow has helped me stay focus on the problem at hand and not worry about constantly checking the terminal, waiting for tests to run.