dideler
2/19/2014 - 2:13 AM

A brief tutorial on getting started with test-driven development

A brief tutorial on getting started with test-driven development

Getting Started with TDD

Project Setup

Generate a new Rails project. Skip unit tests and bundle install.

rails new -T -B tdd

We'll add a couple testing tools.

# Gemfile

group :test, :development do 
  gem 'rspec-rails'
end

group :development do
  gem 'shoulda-matchers'
  gem 'capybara'
end

Note that we added rspec-rails to the development group too. This prevents us from having to type RAILS_ENV=test each time we use the generators and tasks bundled with rspec-rails.

Run Bundle Install

bundle install

Rspec Setup

Create config files and the spec/ directory which houses our tests.

rails g rspec:install

We need to require Capybara in spec_helper.rb as we will be using it to simulate user interaction for our acceptance tests.

# spec/spec_helper.rb

require 'capybara/rails'

Now we are all set up. If we run rspec we will see 0 examples with 0 failures.

Our First Spec

We are going to create a very simple blog. We will use a technique called "outside-in" testing where we will start testing the user experience and work our way to unit testing as required.

Let's create a spec for our first feature - a user managing his or her articles. Our first scenario will involve the user viewing the articles.

# spec/features/user_manages_articles_spec.rb

require 'spec_helper'

feature 'User can manage his or her articles' do
  scenario 'Viewing the articles' do
    visit articles_path
    
    expect(page).to have_content('Articles')
  end
end

Run rspec to watch this test fail.

We need a definition for articles path to fix the issue.

# config/routes.rb

resources :articles, only: [:index]

Now we need a controller to handle requests for article resources.

# app/controllers/articles_controller.rb

def ArticlesController < ApplicationController
end

We need an index action to respond to the request.

# app/controllers/articles_controller.rb

def index
end

We need a template to complete the response.

# app/views/articles/index.html.erb

The template should include the word "Articles"

# app/views/articles/index.html.erb

<h1>Articles</h1>

And success. Our first scenario for our first feature passes. On to our second scenario.

# spec/features/user_manages_articles_spec.rb

scenario 'Adding an article' do
  visit articles_path
    
  click_link 'Add article'
    
  fill_in 'Title', with: 'My first article'
  click_button 'Save'
    
  expect(page).to have_content('My first article')
end

We need to add a link.

# views/articles/index.html.erb

<%= link_to 'Add article', new_article_path %>

We need to update our routes.

# config/routes.rb

resources :articles, only: [:index, :new]

We need to update our controller with the new action.

# app/controllers/articles_controller.rb

def new
end

And add the template...

# app/views/articles/new.html.erb

Create a form with a field to fill in.

# app/views/articles/new.html.erb

<%= form_for @article do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>
<% end %>

Now we need a model

rails g model article title:string
rake db:migrate
rake db:test:prepare

And instantiate it in the new action.

# app/controllers/articles_controller

def new
  @article = Article.new
end

We need a button to save the record.

# app/views/articles/new.html.erb

<%= form_for @article do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>
  
  <%= f.submit 'Save' %>
<% end %>

Update our routes again:

# config/routes.rb

resources :articles, only: [:index, :new, :create]

Update our controller:

# app/controllers/articles_controller.rb

def create
end

Missing template. But we need to create a record and redirect.

# app/controllers/articles_controller.rb

def create
  @article = Article.new(params.require(:article))
end

Whitelist attributes.

# app/controllers/articles_controller.rb

def create
  @article = Article.create(params.require(:article).permit(:title))
end

Redirect

# app/controllers/articles_controller.rb

def create
  @article = Article.create(params.require(:article).permit(:title))
  redirect_to articles_path
end

We expect articles to be shown on the articles page. Let's add them to the template.

# app/views/articles/index.html.erb

<h1>Articles</h1>

<%= @articles.each do |article| %>
  <%= article.title %>
<% end %>

<%= link_to 'Add article', new_article_path %>

Update the index method.

# app/controllers/articles_controller.rb

def index
  @articles = Article.all
end