ruby on rails testing with rspec

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

Upload: bunlong-van

Post on 06-May-2015

846 views

Category:

Software


8 download

DESCRIPTION

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

TRANSCRIPT

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?