Ruby on Rails 3 Testing

Artifacts for Testing Under test

Sub-directories Description
fixtures Contain testing data
functional Testing for individual controllers
integration Integration test for multiple controllers
test_helper.rb Testing configuration
unit Unit testing for models

Ruby on Rails Test Fixtures

Before running tests, Rails load the testing DB data with a pre-defined data called test fixtures

For unit and functional test, by default, load all data under texts/fixtures:

  • Remove any data from the DB table corresponding to the fixture
  • Load the fixture data into the table
  • Load the fixture data into a variable

Test fixtures (accounts.yml)

account1:
  user_name: John Smith
  description: My user 1 description
  premium: false
  income: 100000
  ranking: 1.5
  fee: 9.99
  birthday: 2000-02-21
  login_time: 2011-02-21 18:07:36

account2:
  user_name: Tom Jones
  description: My user 2 description
  premium: false
  income: 80000
  ranking: 2.3
  fee: 9.99
  birthday: 1990-05-21
  login_time: 2011-02-21 18:07:36
  • Each fixture starts with a name followed by indented key/value pairs
  • Data is separated by a blank space
  • # in the first column comment out the whole line

Adding Ruby code in text fixture

<% base_income = 100000 %>
account1:
  income: <%= base_income * 0.9 %>
  login_time: <%= 10.days.ago.to_s(:db) %>

account2:
  income: <%= base_income * 0.85 %>
  login_time: <%= 15.days.ago.to_s(:db) %>

Accessing Test Fixture Data

Access from a predefined variable

accounts(:account1)
accounts(:account1).income

Access from the DB

accounts(:account1).find

Create 1-to-Many Association Test Fixture Objects

Order composes of 1-to-many items

orders.yml

order1:
  order_name: book purchase

order2:
  order_name: cloth purchase

items.yml

o1_item1:
  item_name: ruby book
  order: order1

o1_item2:
  item_name: rails book
  order: order1

o2_item1:
  item_name: jeans
  order: order2

Create Many-to-many Association Test Fixture Objects

Vendors have many clients (or vice versa)

vendors.yml

sev:
  vendor_name: SEV enterprise

sco:
  vendor_name: SCO enterprise

clients.yml with the assocaition

uix:
  client_name: UIX LCC
  vendors: sco, sev

coco:
  client_name: Coco LCC
  vendors: sco

Ruby on Rails Unit Test

Edit

test/unit/vendor_test.rb
require 'test_helper'

class VendorTest < ActiveSupport::TestCase

   # Use fixtures data vendors and clients
   fixtures :vendors, :clients

   test "check data is valid" do

     vendor = Vendor.new

     # Assert not valid since vendor_name is required
     assert !vendor.valid?

     vendor.vendor_name = "E1"
     assert vendor.valid?

     # Compare value
     assert_equal vendor.vendor_name, "E1"

   end

   test "check associations" do
     # Retrieve data from the fixture
     sco = vendors(:sco)

     # Assert SCO should have 2 clients
     assert (sco.clients.length == 2)
   end

   test "check assoication data" do
     sco = vendors(:sco)
     uix = clients(:uix)

     # Get vendors of uix
     companies = uix.vendors

     # Check if sco is one of the vendors
     assert (companies.include? sco)

     coco = clients(:coco)

     # Assert coco vendors is sco
     assert_equal coco.vendors, [sco]

   end

end
Rails Assert Function Description
assert false Assert based on a boolean value
assert_not_nil assigns Assert controller has set the value of the named variable
assert_response :success for http 200, :redirect for 300-399, :missing for 404, :error for 500-599
assert_redirected_to Assert where the request redirect to
assert_difference Assert the row counts changed by n (Default 1)
assert_equal obj1, obj2 obj1 == obj2
assert_not_equal obj1, obj2 obj1 != obj2
assert_same obj1, obj2 obj1.equal?(obj2)
assert_not_same obj1, obj2 not obj1.equal?(obj2)
assert_nil obj obj.nil?
assert_not_nil obj not obj.nil?
assert_match regexp, string Match a regular expression
assert_no_match regexp, string Not match a regular expression
assert_in_delta expecting, actual, delta Actual is within the delta range of expected
assert_throws :SomeError {...} Expect SomeError is thrown
assert_raise ex1, ex2, ... {...} Expect one of the exception is thrown
assert_nothing_raised ex1, ex2, ... { ...} Expect none of the exception in the list is thrown
assert_instance_of class, obj obj is an instance of class
assert_kind_of class, obj obj is kind of class
assert_respond_to obj, :some_method obj implement some_method
assert_operator obj1, operator, obj2 obj1.operator(obj2)
assert_valid record The model object is valid
assert_difference expressions, difference {...} The change of value in expression before and after the code block
assert_no_difference expressions {} Assert no different in the expression before and after the code block
assert_send array Execute a method
flunk Fail a test
assert_recognizes Asserts that the routing of the given path is correct - Testing the Rails routing data
assert_generates Asserts that the generated path is correct - Testing the Rails routing data
assert_template Asserts that the request was handled by the right template file

The assert methods above an optional "message"

assert false, "Failed"

Run the Unit test

Prepare the DB schema

rake db:migrate

Recreate the testing environment DB with db/schema.rb

rake db:test:load

For sub-sequent testing

rake db:test:prepare
  • Check for available migrations and warn the user
  • Load the test schema

Testing rake task

Rake Tasks Description
rake db:test:purge Empty the test database
rake db:test:clone Recreate the test database from current environment
rake db:test:clone_structure Recreate the test databases from the development environment

Run individual test or method

cd ctest
ruby unit/account_test.rb

ruby unit/account_test.rb -n test_register

Run all unit tests

rake test:units

If test "check assoication data" fails, the following message will be displayed

test_check_assoication_data(VendorTest) [/test/unit/vendor_test.rb:46]:
<false> is not true.

Ruby on Rails Functional Test

Edit

test/functional/vendors_controller_test.rb
require 'test_helper'

class VendorsControllerTest < ActionController::TestCase

  test "should get index" do
    # Send a Http Get request to the "index" action
    get :index

    # Assert controller return a HTTP success code
    assert_response :success

    # Assert controller has set a value for the "vendors"
    assert_not_nil assigns(:vendors)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should create vendor" do

    # Send a Post request to the "create" action with data
    # Assert the total vendor in the DB has increased by 1 (default value)
    assert_difference('Vendor.count') do
      post :create, :vendor => {:vendor_name => "E3 Enterprise" }
    end

    # Assert where it is re-directed to
    assert_redirected_to vendor_path(assigns(:vendor))
  end

  test "should show vendor" do
    # Set the http parameter id of the vendor
    # Call "show"
    get :show, :id => vendors(:sev).to_param
    assert_response :success
  end

  test "should get edit" do
    get :edit, :id => vendors(:sev).to_param
    assert_response :success
  end

  test "should update vendor" do
    # Update vendor name
    put :update, :id => vendors(:sev).to_param, :vendor => {:vendor_name => "E3 LLC" }
    assert_redirected_to vendor_path(assigns(:vendor))
  end

  test "should destroy vendor" do
    # Assert row count decrease by 1
    assert_difference('Vendor.count', -1) do
      delete :destroy, :id => vendors(:sev).to_param
    end

    assert_redirected_to vendors_path
  end
end

Simulate a HTTP GET request on a controller's action (for example, show) with pass-in parameters for params

get(:show, {'id' => "133", 'some_param' => "a"})
  • post, put, head, delete method is also available

Passing params and session data

get(:show, {'id' => "133"}, {'some_session_data' => "a"})

Passing params, session data and flash data

get(:show, {'id' => "133"}, {'some_session_data' => "a"}, {'some_flash' => 'flash'})

Hash that can be accessed after the action is completed

flash[:error]
session[:some_session]
cookies[:are_good_for_u]
assigns["something"]      # Accessing @something

Objects that can be accessed

@controller
@request
@response

Make sure the action completed successfully

assert_response :success

Make sure @account is not nil after the action is completed

assert_not_nil assigns(:account)
  • Note assigns:account is invalid for legacy reason

Run the Functional Test

Reload data from the Development DB Schema

rake db:test:prepare
rake test:functionals

Testing 1-to-Many Relationship

class ItemsControllerTest < ActionController::TestCase
  test "should get index" do
    # Send a Get request to the "index" action
    # Instead of just get :index
    get :index, :order_id => orders(:o23).id

    assert_response :success
    assert_not_nil assigns(:items)
  end

  test "should create items" do
    assert_difference('Item.count') do
      post :create, :item => {:item_name => "java" }, :order_id => orders(:o23).id
    end
    assert_redirected_to order_item_path(orders(:o23), assigns(:item))
  end

Ruby on Rails Views Test

Syntax

assert_select(element, [equality], [message])
assert_select(element, selector, [equality], [message])

Example

assert_select 'title', "Page title"
  • Assert the title of the page is correct

Nested select

# Assert that there is 4 "li" nested under "ol" in the whole HTML document
assert_select "ol" do
  assert_select "li", 4
end

# For each "ol" element, there are 2 "li" element
assert_select "ol" do |elements|
  elements.each do |element|
    assert_select element, "li", 2
  end
end

Example

assert_select 'title', "Page Title"  # Assert the page title is
Other method Description
assert_select_encoded Assertion on encoded HTML
css_select Use CSS selector to select element

Ruby on Rails Integration Test

Generate an integration testing skeleton

rails generate integration_test vendors
      exists  test/integration/
      create  test/integration/vendors_test.rb

Edit

test/integration/vendors_test.rb
require 'test_helper'

class VendorsTest < ActionController::IntegrationTest
  fixtures :accounts

  test "register" do
    get '/vendors/new'

    # check there are text input boxes for vendor_name
    assert_select "input[type=text][name='vendor[vendor_name]']"

    assert_difference('Vendor.count') do
      post '/vendors/create', :vendor => {
        :vendor_name => "INN LCC"
      }
    end

    assert_redirected_to "/vendors/#{assigns(:vendor).id}"
    follow_redirect!

    # Search the HTML output contain the regular expression
    assert_select "p", /INN LCC/
  end
end

Integration Testing Methods

Method Description
https? Is a simulated HTTPS request
https! Simulate a HTTPS request
host! Set the host name
redirect? Last request was a redirect
follow_redirect! Follows a single redirect
request_via_redirect Make an HTTP request and follow the next redirects
post_via_redirect Make a HTTP POST request and follow the next redirects
get_via_redirect Make a HTTP GET request and follow any subsequent redirects
put_via_redirect Make a HTTP PUT request and follow any subsequent redirects
delete_via_redirect Make a HTTP DELETE request and follow any subsequent redirects
open_session Opens a new session

Run Integration Test

rake test:integration

Rake Testing Target

Target Description
rake test Runs all tests
rake test:benchmark Benchmark the performance tests
rake test:functionals Runs all functional tests
rake test:integration Runs all the integration tests
rake test:plugins Run all the plugin tests
rake test:profile Profile the performance tests
rake test:recent Tests recent changes
rake test:uncommitted Runs all the tests which are uncommitted to Subversion
rake test:units Runs all the unit tests

Route Unit Testing

Assert the options generated the specific path

assert_generates("/accounts/1", { :controller => "accounts", :action => "show", :id => "1" })

Assert Rails routes the given path to the specific controller/action

assert_recognizes({ :controller => "accounts", :action => "show", :id => "1" }, "/accounts/1")
assert_recognizes({ :controller => "accounts", :action => "create" }, { :path => "accounts", :method => :post })
assert_recognizes new_account_url, { :path => "accounts", :method => :post }

Combined check for assert_generates & assert_recognizes

assert_routing({ :path => "accounts", :method => :post }, { :controller => "accounts", :action => "create" })

Test setup/teardown

  def setup
     # Run before each test
  end

  def teardown
     # Run after each test
  end

Use a method symbol

 setup :method1

 def method1
 end

Ruby on Rails Performance Testing

Default performance testing file

test/performance/browsing_test.rb

Generate testing template

rails generate test unit:performance accountpage

create  test/performance/accountpage_test.rb
require 'test_helper'
require 'rails/performance_test_help'

class AccountPerformanceTest < ActionController::PerformanceTest
  def setup
    # Login
    login_as(:lifo)
  end

  def test_account_home
    get '/accounts'
  end

  def test_creat_new_account
    post '/accounts', :post => { :body => 'testing' }
  end

  def test_some_method
    # Add any Ruby on Rails testing codes
    ...
  end
end

Benchmarking the test

rake test:benchmark
  • Will run the test 4 times by default
  • Test results are generated in /tmp/performance directory
  • Result are appended to previous test to show historical trend
  • Generate a CSV report and a plain report
  • Test are run in testing environment

Profiling the test

rake test:profile

Command Line Tool

Benchmark methods

rails benchmarker 10 'Account.all' 'Account.new'

Profile methods

rails profiler 'Account.all' 5
  • Run the test 5 times

Rails Performance Helper Method

Benchmark model

Project.benchmark("Benchmark model") do
  project = Project.create("name" => "Test2")
  project.create_manager("name" => "Jonathan")
  project.milestones << Account.find(:all)
end
  • Information is logged in the log file
  • Log file contains other performance information logged by Rails

Benchmark controller

def process_projects
  self.class.benchmark("Processing projects") do
    Project.process(params[:project_ids])
    Project.update_cached_projects
  end
end

Benchmark a view

<% benchmark("Benchmark view") do %>
  <%= render :partial => @projects %>
<% end %>