Ruby on Rails 3 Testing with RSpec 2

Install RSpec for Ruby on Rails 3

Install Rspec-rails

sudo gem install rspec-rails
  • RSpec is a behavior driven development (BDD) framework for low level testing in the Ruby language

Using RSpec to Test a Rails 3 Application

Create a new Rails application store

rails new store -d mysql

Edit the Gemfile

cd store
vi Gemfile

Include the Cucumber gems in a Rails 3 application

group :test, :development do
  gem "rspec-rails", "~> 2.4"
  gem 'capybara'
end

Install the Gems

bundle install

Edit the DB connection information and put the valid DB configuration information

vi config/database.yml

Create the MySQL DB

rake db:create
rake db:migrate

Bootstrap a Rails 3 application with RSpec

rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb

Testing Ruby on Rails Application with RSpec

Generate a model

rails generate model order name:string
      invoke  active_record
      create    db/migrate/20110510190917_create_orders.rb
      create    app/models/order.rb
      invoke    rspec
      create      spec/models/order_spec.rb

Create the DB table

rake db:migrate

General Syntax for RSpec

describe "something" do
  it "does something" do
    ...
  end
end
describe "something" do
  context "in one context" do
    it "does something" do
     ...
    end
  end
  context "in another context" do
     ...
  end
end

Edit the RSpec Test

vi spec/models/order_spec.rb

Edit the RSpec testing code

spec/models/order_spec.rb
require 'spec_helper'

describe Order, "shipping methods" do
  it "returns the order's shipping mode" do
    order = Order.create!
    order.shipping = 'express'
    order.shipping.should == 'express'
  end
end

Smoke test

rake spec

RSpec Core

Create a helper method for the test

let(:count) { @total += 1 }

Before, After & Around

Run test setup code

before(:each) do
    @order = Order.new
end

Before and after test setup

before(:each) # run before each test
before(:all)  # run one time only

after(:each) # run after each test
after(:all)  # run one time only

A around test setup

class Tx
  def self.transaction
    puts "open tx"
    yield
    puts "close tx"
  end
end

describe "around filter" do
  around(:each) do |example|
    Tx.transaction(&example)
  end

  ...
end

RSpec Matcher

RSpec should and should_not Syntax

order.should matcher(expected)
order.should_not matcher(expected)

Example

"A".should eq("A")
"A".should_not eq("B")

Check if empty or exist in RSpec

[].should be_empty
[1].should_not be_empty

test_result.should exist
test_result.should_not exist

Check the type in RSpec

120.should be_a_kind_of(Fixnum)

Check specific values in RSpec

test_result.should be
test_result.should be_true
test_result.should be_false
test_result.should be_nil
test_result.should_not be_nil
test_result.should eql(1)
test_result.should_not eql(1)

Collection key checking in RSpec

{:a => "A"}.should have_key(:a)

Check the collection size in RSpec

# Passes if test_result.orders.size == 11
test_result.should have(10).orders

# Passes if [1,2].length == 2
[1,2].should have(2).items

[1,2].should have >= 1

# Check string length
"1234".should have(4).characters

Check result within 0.5 of 3.0

test_result.should be_close(3.0, 0.5)

Check array content in RSpec

[1,2,3,4,5].should include(2,3)

Verify test result with regular expression in RSpec

test_result.should match(/[0-9]+)

Check if an exception is raised in RSpec

lambda { some_action }.should raise_error
lambda { some_action }.should raise_error(SomeError)
lambda { some_action }.should raise_error(SomeError) { |error| ... }
lambda { some_action }.should raise_error(SomeError, "some errors happened")

RSpec Mock Up

RSpec provides mock up objects used for testing

Create a RSpec double object

double_order = double("Order")
  • double means create a Mock up (Not the floating value)

Alternatively, use the stub method to create a Mock up object with a stub method

obj.stub(:text).and_return('evaluated the return value immediately')
obj.stub(:text) { 'evaluated the value when it is called' }

obj.text.should eq('evaluated the value when it is called')
  • Create a method text for the Mock up double object
  • Return a value for the method price

Create a RSpec object return a value in a mock up method

describe "a mock up object" do
  it "returns a value" do
    object = Object.new
    object.stub(:value) do |arg|
      if arg == 1
        "odd"
      elsif arg == 2
        "eve"
      end
    end

    object.value(1).should eq("odd")
    object.value(2).should eq("even")
  end
end

Raise an exception in the RSpec double

obj.stub(:value).and_raise("something_wrong")
obj.stub(:value).and_throw(:something_wrong)

RSpec Message Expectation

obj.value is expected to be called during the testing

obj.should_receive(:value)

# obj.value will retun "this ... return"
obj.should_receive(:value) { 'this is the value to return' }

Trace method calls with Mock up double in RSpec

describe Order do
  context "when closed" do
    it "logs an 'order shipped' message" do
      logger = double()
      order = Order.new
      order.logger = logger

      logger.should_receive(:order_shipped).with(order)

      order.ship
    end
  end
end
  • Create a logger double and assigned it to the order's logger
  • should_receive will assert true if order.ship call logger.order_shipped with order as the parameter

Count the number of calls

obj.should_receive(:value).twice
obj.should_receive(:value).exactly(2).times
obj.should_receive(:value).at_least(:once)
obj.should_receive(:value).at_least(2).times

obj.should_receive(:value).at_most(:twice)
obj.should_receive(:value).at_most(2).times

Raise exception

obj.should_receive(:value) { raise "some error" }
obj.should_receive(:value) { throw :some_error }

RSpec Rails

RSpec Model Spec

Use RSpec to test Rails' Model

require "spec_helper"

describe Order do
  context "with 2 or more items" do
    it "display order items" do
      order = Order.create
      item1 = order.item("item 1")
      item2 = order.item("item 2")
      order.reload.items.should eq([item1, item2])

      order.should have(2).errors_on(:name)
    end
  end
end

RSpec Controller Spec

Test Rails' controller using RSpec

describe OrdersController do
  describe "GET index" do
    it "assigns @orders" do
      order = Order.create
      get :index
      assigns(:orders).should eq([order])
    end

    it "renders the index template" do
      get :index
      response.should render_template("index")
    end
  end
end

RSpec Request Spec

Use RSpec to test HTTP request

require "spec_helper"

describe "Order management" do

  it "creates an Order and redirects to the Order's index page" do
    get "/orders/new"
    response.should render_template(:new)

    post "/orders", :order => {:name => "Order 1"}

    response.should redirect_to(assigns(:order))
    follow_redirect!

    response.should render_template(:show)
    response.body.should include("Order was successfully created.")
  end

end
  • render_template verifies what view is rendered
  • redirect_to verifies the redirection

RSpec Route Spec

Use RSpec to verify rails routing

{ :get => "/orders/24444/ship" }.
  should route_to(
    :controller => "orders",
    :action => "index"
  )

Verify that the route is not available

{ :delete => "/orders/24444" }.should_not be_routable

RSpec View Spec

Use RSpec to test the View

require "spec_helper"

describe "orders/index.html.erb" do
  it "displays all the orders" do
    assign(:orders, [
      stub_model(Order, :name => "order 1"),
      stub_model(Order, :name => "order 2")
    ])

    render

    rendered.should =~ /order 1/
    rendered.should =~ /order 2/
  end
end

mock_model & stub_model

Create a Mock up model object similar to ActiveRecord

require "spec_helper"

describe "mock_model Order" do
  let(:order) do
    mock_model Order, :value => "something",
                      :save => true
  end

  it "access the value method" do
    order.vale.should eq("something")
  end

  it "access ActiveRecord method" do
    order.save.should eq(true)
  end
end

Generate an instance of ActiveRecord

let(:order) do
    stub_model Order, :id => 10
end