Testing

Rails Testing Landscape

Rails Test Test tools Interface
  Selenium, Watir HTTP from Browser (IE, Firefox)
  WWW::Mechanize, Webrat HTTP
Integration Webrat, RSpec Stories, Cucumber Rails Dispatcher
Functional RSpec, Shoulda Controller
Unit RSpec, Shoulda Model

Test::Unit::TestCase

Unit Tests

Unit Test Example


class UserTest < ActiveSupport::TestCase
  fixtures :customers, :services, :users  
  setup do
    @user = users(:joe)
  end
  test "should have a unique name" do
    assert_no_difference 'User.count' do
      user = User.create(:name => @user.name)
      assert !user.valid?
      assert user.errors.on(:name)
    end
  end
end

test_helper.rb


ENV["RAILS_ENV"] = "test" 
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'

class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  self.use_instantiated_fixtures  = false
  fixtures :all
end

Test Data Through Fixtures

Fixture Example: articles.yml


ruby:
  header: Introductory article on Ruby
  body: bla bla bla
  user: joe # Article belongs_to :user
  created_at: <%= 5.days.ago.to_s :db %>

rails:
  header: Ruby on Rails 101
  body: bla bla bla
  user: peter
  created_at: <%= 5.days.ago.to_s :db %>

Assertions


assert(actual, comment) # Asserts truth
assert_equal(expected, actual, comment)
assert_in_delta(expected_float, actual_float, delta, message)
assert_match(pattern, string, message)
assert_nil(object, message)/assert_not_nil
assert_raise(Exception, ..., message) { block ... }
assert_difference(expressions, difference = 1, &block)

Functional Testing of Controllers

Functional Test Example


class ArticlesControllerTest < ActionController::TestCase
  fixtures :users, :comments
  test "should get rss action" do
    get :rss, :id => users(:quentin)
    assert_response :success
    assert_select "rss > channel" do
      assert_select "title", /Recent comments/
      assert_select "item", 1
      assert_select "item > title", Regexp.new(users(:aaron).login)
      assert_select "item > description", users(:quentin).comments.first.body
    end
  end
end

Assertions in Functional Tests


assert_response :success|:redirect|:missing|:error
assert_redirected_to(:controller => 'blog', :action => 'list')
assert_template 'store/index'
assert_not_nil assigns(:items)
assert session[:user]
assert_not_nil flash[:notice]

assert_select


assert_select "p.warning" # <p class="warning">...</p>
assert_select "p#warning" # <p id="warning">...</p>
assert_select "html p.warning" # Ancestor chaining
assert_select "html > body > p.warning" # Direct parent chaining
assert_select "div#cart table tr", 3 # Integer, n times
assert_select "div#cart table tr", 3..5 # Range, n times
assert_select "div#cart table tr", false # Not present on page
assert_select "div#cart table tr td#price", "$23" # Tag contents
assert_select "div#cart table tr td#price", /23/ # Regexp for tag contents

assert_select "form input" do
  assert_select "[name=?]", /.+/  # Not empty
end

Integration Tests

Integration Test Example


class TracerBulletTest < ActionController::IntegrationTest
  test "main use case" do
    get("/user/login")
    assert_response :success

    post("user/login", :email => @email, :password => @password)
    assert_redirected_to :controller => '/general'
    follow_redirect!
    assert_response :success

    post("/contacts/search", :q => 'sandler new york')
    assert_response :success
  end
end

Integration Test Using a DSL


test "buying a product" do
  dave = regular_user
  dave.get "/store/index" 
  dave.is_viewing "index" 
  dave.buys_a @ruby_book
  dave.has_a_cart_containing @ruby_book
  dave.checks_out DAVES_DETAILS
  dave.is_viewing "index" 
  check_for_order DAVES_DETAILS, @ruby_book
end

Integration Test DSL


def regular_user
  open_session do |user|
    def user.is_viewing(page)
      assert_response :success
      assert_template page
    end  
    def user.buys_a(product)
      xml_http_request :put, "/store/add_to_cart", :id => product.id
      assert_response :success 
    end
    ...
  end  
end

Running Tests


rake # runs all tests
rake test:units
rake test:functionals
rake test:integration
ruby test/unit/user_test.rb # run single test

Stubbing and Mocking

Mocha Example


product = Product.new
Product.expects(:find).with(1).returns(product)
assert_equal product, Product.find(1)

Product.any_instance.stubs(:name).returns('stubbed_name')
product = Product.new
assert_equal 'stubbed_name', product.name

object = mock()
object.expects(:expected_method).with(:p1, :p2).returns(:result)
assert_equal :result, object.expected_method(:p1, :p2)

Submitting Forms and Clicking Links

rcov


# Installation of rcov:
sudo gem install rcov
ruby script/plugin install http://svn.codahale.com/rails_rcov
rake test:test:rcov

Heckle


# Installation of Heckle:
sudo gem install heckle
heckle -t test/functional/comments_controller_test.rb CommentsController create

AJAX and RJS Testing

HTML Validation and Link Checking

Using the Rails Tests as Documentation

BDD: From Verification to Specification

RSpec Example


describe Contact do
  before(:each) do
    @contact = contacts(:peter)
  end
  describe "creating" do
    it "can be created with source, email and invitation_code" do
      lambda do
        contact = Contact.create(create_params)
        contact.reload.email.should == create_params[:email]
      end.should change(Contact, :count).by(1)
    end
  end
end