46
RSpec & Rails RSpec & Rails Bunlong Van – Rubyist/Rails Devel Bunlong Van – Rubyist/Rails Devel Mail: Mail: [email protected] Blog: http://geekhmer.github.io Blog: http://geekhmer.github.io

Ruby on Rails testing with Rspec

Embed Size (px)

DESCRIPTION

Presenting of using Rspec, Mock and Stub in Ruby on Rails Project, and comparing between Mock and Stub.

Citation preview

Page 1: Ruby on Rails testing with Rspec

RSpec & RailsRSpec & RailsBunlong Van – Rubyist/Rails DeveloperBunlong Van – Rubyist/Rails DeveloperMail: Mail: [email protected]: http://geekhmer.github.ioBlog: http://geekhmer.github.io

Page 2: Ruby on Rails testing with Rspec

Cover

- What is Rspec?

- RSpec features

- RSpec in action

- Stubs & Mocks

- Stubs & Mocks using RSpec

Page 3: Ruby on Rails testing with Rspec

What is RSpec ?

- Testing framework for Ruby on Rails.

- Replacement for RoR built-in testing tool.

Page 4: Ruby on Rails testing with Rspec

TestUnit

class Calculator < Test::Unit::TestCase def test_addition assert_equal(8, Calculator.new(6, 2).addition) end

def test_subtraction assert_same(4, Calculator.new(6, 2).subtraction) endend

Page 5: Ruby on Rails testing with Rspec

RSpec

desribe Calculator do let(:calculator) { Calculator.new(6,2) }

it "should return 8 when adding 6 and 2" do calculator.addition.should eql(8) end

it "should return 4 when subtracting 2 from 6" do calculator.subtraction.should eql(4) endend

Page 6: Ruby on Rails testing with Rspec

RSpec basics

describe MyClass do # creates initial scope before do @object = MyClass.new end

describe "#some_method" do # creates new scope it "should ..." do @object.should ... end

context "when authorized" do # creates new scope before do @object.authorized = true end

Page 7: Ruby on Rails testing with Rspec

RSpec basics (Con.)

it "should ..." do @object.should ... end end

context "when not authorized" do # creates new scope it "should ..." do @object.should ... end end end

it "should ..." do pending "Fix some model first" # creates pending test endend

Page 8: Ruby on Rails testing with Rspec

Why RSpec?

- Readability

- Tests documentation and failure messages

Page 9: Ruby on Rails testing with Rspec

Readability

describe Campaign do

it "should be visible by default" do campaign.should be_visible end

it "should have performances visible by default" do campaign.performances_visible.should be_true end

it "should not be published at start" do campaign.should_not be_published end

Page 10: Ruby on Rails testing with Rspec

Readability (Con.)

it "should not be ready to publish at start - without performances, etc" do campaign.should_not be_ready_to_publish end

describe "#allowed_performance_kinds" do it "should allow all by default" do campaign.allowed_performance_kinds.should == Performance.kinds end endend

Page 11: Ruby on Rails testing with Rspec

Tests documentation and failure messages

Page 12: Ruby on Rails testing with Rspec

When tests output matter

- Built-in profiler

- Test run filters

- Conventions

- Built-in matchers

Page 13: Ruby on Rails testing with Rspec

Built-in profiler

Page 14: Ruby on Rails testing with Rspec

Test run filters

def old_ruby RUBY_VERSION != "1.9.2"end

describe TrueClass do it "should be true for true", :if => old_ruby do true.should be_true end

it "should be true for String", :current => true do "".should be_true end

Page 15: Ruby on Rails testing with Rspec

Test run filters (Cons.)

it "should be true for Fixnum" do 0.should be_true endend

Page 16: Ruby on Rails testing with Rspec

Conventions

Page 17: Ruby on Rails testing with Rspec

Built-in matchers

target.should satisfy {|arg| ...}target.should_not satisfy {|arg| ...}target.should equal <value>target.should not_equal <value>target.should be_close <value>, <tolerance>target.should_not be_close <value>, <tolerance>target.should be <value>target.should_not be <value>target.should predicate [optional args]target.should be_predicate [optional args]target.should_not predicate [optional args]target.should_not be_predicate [optional args]target.should be < 6target.should be_between(1, 10)

Page 18: Ruby on Rails testing with Rspec

Built-in matchers (Cons.)

target.should match <regex>target.should_not match <regex>target.should be_an_instance_of <class>target.should_not be_an_instance_of <class>target.should be_a_kind_of <class>target.should_not be_a_kind_of <class>target.should respond_to <symbol>target.should_not respond_to <symbol>

lambda {a_call}.should raise_errorlambda {a_call}.should raise_error(<exception> [, message])lambda {a_call}.should_not raise_errorlambda {a_call}.should_not raise_error(<exception> [, message])

Page 19: Ruby on Rails testing with Rspec

Built-in matchers (Cons.)

proc.should throw <symbol>proc.should_not throw <symbol>target.should include <object>target.should_not include <object>target.should have(<number>).thingstarget.should have_at_least(<number>).thingstarget.should have_at_most(<number>).thingstarget.should have(<number>).errors_on(:field)

expect { thing.approve! }.to change(thing, :status) .from(Status::AWAITING_APPROVAL) .to(Status::APPROVED)

expect { thing.destroy }.to change(Thing, :count).by(-1)

Page 20: Ruby on Rails testing with Rspec

Problems ?

- Hard to learn at the beginning

- Routing tests could have more detailed failure messages

- Rails upgrade can break tests compatibility

Page 21: Ruby on Rails testing with Rspec

RSpec in action

- Model specs (placed under spec/models director)

- Controller specs (placed under spec/controllers directory)

- Helper specs (placed under spec/helpers directory)

- View specs (placed under spec/views directory)

- Routing specs (placed under spec/routing directory)

Page 22: Ruby on Rails testing with Rspec

Model specs

describe Campaign do

it "should be visible by default" do campaign.should be_visible end

it "should have performances visible by default" do campaign.performances_visible.should be_true end

it "should not be published at start" do campaign.should_not be_published end

Page 23: Ruby on Rails testing with Rspec

Model specs (Cons.)

it "should not be ready to publish at start - without performances, etc" do campaign.should_not be_ready_to_publish end

describe "#allowed_performance_kinds" do it "should allow all by default" do campaign.allowed_performance_kinds.should == Performance.kinds end endend

Page 24: Ruby on Rails testing with Rspec

Controller specs

describe SessionsController do render_views

describe "CREATE" do context "for virtual user" do before do stub_find_user(virtual_user) end

it "should not log into peoplejar" do post :create, :user => {:email => virtual_user.email,

:password => virtual_user.password } response.should_not redirect_to(myjar_dashboard_path) end end

Page 25: Ruby on Rails testing with Rspec

Controller specs (Cons.)

context "for regular user" do before do stub_find_user(active_user) end

it "should redirect to myjar when login data is correct" do post :create, :user => {:email => active_user.email,

:password => active_user.password } response.should redirect_to(myjar_dashboard_path) end

end endend

Page 26: Ruby on Rails testing with Rspec

Helper specs

describe CampaignsHelper do let(:campaign) { Factory.stub(:campaign) } let(:file_name) { "meldung.jpg" }

it "should return the same attachment URL as paperclip if there is no attachment" do campaign.stub(:featured_image_file_name).and_return(nil) helper.campaign_attachment_url(campaign, :featured_image). should eql(campaign.featured_image.url) end

it "should return the same attachment URL as paperclip if there is attachment" do campaign.stub(:featured_image_file_name).and_return(file_name)

Page 27: Ruby on Rails testing with Rspec

Helper specs (Cons.)

helper.campaign_attachment_url(campaign, :featured_image). should eql(campaign.featured_image.url) endend

Page 28: Ruby on Rails testing with Rspec

View specs

# view at views/campaigns/index.html.erb

<%= content_for :actions do %><div id="hb_actions" class="browse_arena"> <div id="middle_actions"> <ul class="btn"> <li class="btn_blue"><%= create_performance_link %></li> </ul></div></div><% end %><div id="interest_board_holder"> <%= campaings_wall_template(@campaigns) %></div>

Page 29: Ruby on Rails testing with Rspec

View specs (Cons.)

# spec at spec/views/campaigns/index.html.erb_spec.rb

describe "campaigns/index.html.erb" do let(:campaign) { Factory.stub(:campaign) }

it "displays pagination when there are more than 20 published campaigns" do assign(:campaigns, (1..21).map { campaign }. paginate(:per_page => 2) )

render rendered.should include("Prev") rendered.should include("Next") endend

Page 30: Ruby on Rails testing with Rspec

Routing specs

describe "home routing", :type => :controller do it "should route / to Home#index" do { :get => "/" }.should route_to(:controller => "home", :action => "index",

:subdomain => false) end

it "should route / with subdomain to Performances::Performances#index" do { :get => "http://kzkgop.test.peoplejar.net" }.

should route_to(:namespace => nil, :controller => "performances/performances", :action => "index")

endend

Page 31: Ruby on Rails testing with Rspec

Routing specs (Cons.)

describe "error routing", :type => :controller do it "should route not existing route Errors#new" do { :get => "/not_existing_route" }.should route_to(:controller => "errors",

:action => "new", :path => "not_existing_route") endEnd

describe "icebreaks routing" do it "should route /myjar/icebreaks/initiated to

Icebreaks::InitiatedIcebreaks#index" do { :get => "/myjar/icebreaks/initiated" }.should

route_to(:controller => "icebreaks/initiated_icebreaks", :action => "index")

endend

Page 32: Ruby on Rails testing with Rspec

Routing specs (Cons.)

describe "admin routing" do it "should route /admin to Admin::Base#index" do { :get => "/admin" }.should route_to(:controller => "admin/welcome",

:action => "index") endend

Page 33: Ruby on Rails testing with Rspec

Stubs & Mocks

Page 34: Ruby on Rails testing with Rspec

Back to unit test assumptions

- A unit is the smallest testable part of an application

- The goal of unit testing is to isolate each part of the program and show that the individual parts are correct

- Ideally, each test case is independent from the others

Page 35: Ruby on Rails testing with Rspec

you.should use_stubs!

- Isolate your unit tests from external libraries and dependencies

- Propagate skinny methods which has low responsibility

- Single bug should make only related tests fail

- Speed up tests

Page 36: Ruby on Rails testing with Rspec

PeopleJar is using

Page 37: Ruby on Rails testing with Rspec

Are there any problems ?

- Writing test is more time consuming

- Need to know stubbed library internal implementations

- Need to write an integration test first

Page 38: Ruby on Rails testing with Rspec

Stubs in action

User.stub(:new) # => nilUser.stub(:new).and_return(true)

user_object = User.newuser_object.stub(:save).and_return(true)User.stub(:new).and_return(user_object)

user_object.stub(:update_attributes).with(:username => "test").and_return(true)User.stub(:new).and_return(user_object)

User.any_instance.stub(:save).and_return(true)

# User.active.paginateUser.stub_chain(:active, :paginate).and_return([user_object])

Page 39: Ruby on Rails testing with Rspec

Stubs in action

User.stub(:new) # => nilUser.stub(:new).and_return(true)

user_object = User.newuser_object.stub(:save).and_return(true)User.stub(:new).and_return(user_object)

user_object.stub(:update_attributes).with(:username => "test").and_return(true)User.stub(:new).and_return(user_object)

User.any_instance.stub(:save).and_return(true)

# User.active.paginateUser.stub_chain(:active, :paginate).and_return([user_object])

Page 40: Ruby on Rails testing with Rspec

Stubs in action (Cons.)

user_object.stub(:set_permissions).with(an_instance_of(String), anything).and_return(true)user_object.unstub(:set_permissions)# user_object.set_permissions("admin", true) # => true (will use stubbed method)# user_object.set_permissions("admin") # => false (will call real method)

Page 41: Ruby on Rails testing with Rspec

Mocks in action

User.should_receive(:new) # => nilUser.should_receive(:new).and_return(true)User.should_not_receive(:new)

user_object = User.newuser_object.should_receive(:save).and_return(true)User.stub(:new).and_return(user_object)

user_object.should_receive(:update_attributes).with(:username => "test").and_return(true)User.stub(:new).and_return(user_object)

User.any_instance.should_receive(:save).and_return(true) # !

Page 42: Ruby on Rails testing with Rspec

Mocks in action (Cons.)

user_object.should_receive(:update_attributes).once # defaultuser_object.should_receive(:update_attributes).twiceuser_object.should_receive(:update_attributes).exactly(3).times

user_object.should_receive(:set_permissions).with(an_instance_of(String), anything)# user_object.set_permissions("admin", true) # Success# user_object.set_permissions("admin") # Fail

Page 43: Ruby on Rails testing with Rspec

What's the difference between Stubs and Mocks

- Mocks are used to define expectations and verify them

- Stubs allows for defining eligible behavior

- Stubs will not cause a test to fail due to unfulfilled expectation

Page 44: Ruby on Rails testing with Rspec

In practice - Stub failure

describe ".to_csv_file" do it "should generate CSV output" do User.stub(:active).and_return([user]) User.to_csv_file.should == "#{user.display_name},#{user.email}\n" endend

Page 45: Ruby on Rails testing with Rspec

In practice - Mock failure

describe "#facebook_uid=" do it "should build facebook setting instance if not exists when setting uid"

do user.should_receive(:build_facebook_setting).with(:uid => "123") user.facebook_uid = "123" endend

Page 46: Ruby on Rails testing with Rspec

Question?