Build Robust & Production Quality Applications - Lesson 2: Single Assertion Principle

Single Assertion principle states:

A test suite should only have one assertion, so that if there is a failure they fail separately. If you find yourself using "and" then you are probably testing too much at once:

get :index
  assigns(:todos).should == (cook, sleep)

There is an exception to this rule - when the assertions are very related concerning the same object:

get :new
  assigns(:todo).should be_new_record
  assigns(:todo).should be instance_of(Todo)

Build Robust & Production Quality Applications - Lesson 2: Alternative Style of Rspec

An Alternative Style of Rspec

  • using let, before, subject, etc.

Uses lazy evaluation which means it doesn't evaluate the :todo or the subject until it has to ...namingly when it reaches it or before.

To avoid lazy evaluation, you could do:

let!(:todo) {Todo.create(name: "cook dinner")}

Pros:

is more concise and separates the common set up code using lets

Cons:

For a lengthy test suite it may be hard to connect the test with the description.

Using let

Step 1. Eliminate the repetition of Todo.create in each test:

describe "#display_text" do
  let(:todo) { Todo.create(name: "cook dinner") }

Step 2. Replace todo.display_ text

describe "#display_text" do
  let(:todo) { Todo.create(name: "cook dinner") }
  let(:subject) { todo.display_text }

or

Step3. Replace todo.display_text

describe "#display_text" do
  let(:todo) { Todo.create(name: "cook dinner") }
  subject { todo.display_text }

Using before for case specific tests

context "displays the name when there's no tags" do
  it "displays the name when there's no tags" do
    subject.should == "cook dinner"
  end
end

context "displays the only tag with word 'tags' when there's one tag"
  before do
    todo.tags.create(name: "home")
    todo.tags.create(name: "urgent")
  end
  it "displays name with multiple tags" do
    subject.should == "cook dinner (tags: home, urgent)"
  end
end

Change lengthy context to more succinct:

context "displays the only tag with word 'tags' when there's one tag"

to =>

context "multiple tags"

Remove the "it -text-" because its in context with the description

context "one tag" do
  before { todo.tags.create(name: "home") }
  it { should == "cook dinner (tag: home)" }
end

Build Robust & Production Quality Applications - Lesson 2: Alternative Style of Rspec

Techniques you can use to grow your test suite:

Testing tags (many to many relationship with Todos) * When testing for numbers, you want to use the 0, 1, many, and boundary_condition (if there is one)

describe "display_text" do
  it "displays the name when there's no tags"
  it "displays the only tag with word 'tag' when there's one tag"
  it "displays name with multiple tags"
  it "displays up to four tags"
  end
end
describe "#display_text" do
  it "displays the name when there's no tags" do
   todo = Todo.create(name: "cook dinner")
   todo.display_text.should == "cook dinner"
  end

  it display the only tag with the word 'tag' when there's one tag do
    todo = Todo.create(name: "cook dinner")
    todo.tags.create(name: "home")
    todo.display_text.should == "cook dinner (tags: home)"
  end

  it "displays name with multiple tags" do
    todo.tags.create(name: "cook dinner")
    todo.tags.create(name: "home")
    todo.tags.create(name: "urgent")
    todo.display_text.should == "cook dinner(tags: home, urgent)"
  end

  it "displays up to four tags" do
    todo = Todo.create(name: "cook dinner")
    todo.tags.create(name: "home")
    todo.tags.create(name: "urgent")
    todo.tags.create(name: "help")
    todo.tags.create(name: "book")
    todo.tags.create(name: "patience")
    todo.displat_text.should == "cook dinner (tags: home, urgent, help, book, more...)"
  end
end

Build Robust & Production Quality Applications - Lesson 2: Refactor in TDD

Add feature in the Red and refactor in the green.

  1. Pull out anything complex into a separate method and give it a name
  2. Clean up public facing methods.. pulling complex code into private methods

Example:

class Todo < ActiveRecord:;Base
  has_many :taggings
  has_many :tags, through: :taggings
  validates_presence_of :name


  def name_only?
    description.blank?
  end

  def display_text
    name + tag_text
  end


  private

  def tag_text
    if tags.any?
      " (#{tags.one? ? 'tag' : 'tags' } : #{tags.map(&:name).first(4).join(", ")}
      #{', more...' if tags.count > 4})"
    else
      ""
    end
  end
end

Build Robust & Production Quality Applications - Lesson 2: Writing Controller Tests

With any test, there are typically 3 actions:

  1. Set up the data required for the action
  2. Perform the action
  3. Verify the the expected result

Controller Tests

  • are typically considered more functional bc you're pulling together multiple components

You typically never have to tests the render or redirect views portion of an action because that is testing the functionality of rails rather than the code we write. The goal of testing is to test the code we write only.

You do, however want to test redirect_to for unauthorized users.

Also if you dont wan't to rerun all the tests in your suite and just run one in particular you can specify "line:27" and it will run the test on that line.

rspec spec/controllers/todos_controller_spec.rb 27
class TodoController < ApplicationController

  def index
    @todos = Todo.new
  end

  def new
    @todo = Todo.new
  end

  def create
    @todo = Todo.new(params[:todo])
    if @todo.save
      redirect_to root_path
    else
    render :new
    end
  end
end

Steps:

  1. Create directory: controllers
  2. Create spec: todosconrollerspec.rb
  3. Controller typically have two goals... render and redirect
require 'spec_helper'

describe TodoController do

  describe "GET index" do
  it "set the @todos variable" do
    cook = Todo.create(name: "cook")
    sleep = Todo.create(name: "sleep")

    get :index
    assigns(:todos).should == (cook, sleep)
  end

  it "render the index template" do
    get :index
    response.should render_template :index
    end
  end

  describe 'GET new' do
    it "sets  the @todo variable" do
      get :new
      assigns(:todo).should be_new_record
      assigns(:todo).should be_instance_of(Todo)
    end
    it "renders the new template" do
      get :new
      response.should render_template :new
    end
  end

  describe "POST create" do
    it "creates the todo record when the input is valid" do
      post :create, todo: {name: "cook", description: "I like to cook"}
      Todo.first.name.should == "cook"
      Todo.first.description.should == "I like cooking"
    end

    it "redirects to the root path when the input is valid" do
        post :create, todo: {name: "cook", description: "I like cooking"}
        response.should redirect_to root_path
    end

    it "does not create a todo when the input is invalid" do
        post :create, todo: {description: "I like cooking"}
        Todo.count.should == 0
    end

    it "renders the new template when the input is invalid" do
      post :create, todo: {description: "I like cooking"}
      response.should render_template :new
    end
end