Ruby on Rails testing with Rspec

1,601 views

Published on

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

Published in: Software
  • Be the first to comment

Ruby on Rails testing with Rspec

  1. 1. RSpec & Rails Bunlong Van – Rubyist/Rails Developer Mail: bunlong.van@gmail.com Blog: http://geekhmer.github.io
  2. 2. Cover - What is Rspec? - RSpec features - RSpec in action - Stubs & Mocks - Stubs & Mocks using RSpec
  3. 3. What is RSpec ? - Testing framework for Ruby on Rails. - Replacement for RoR built-in testing tool.
  4. 4. 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) end end
  5. 5. 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) end end
  6. 6. 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
  7. 7. 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 end end
  8. 8. Why RSpec? - Readability - Tests documentation and failure messages
  9. 9. 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
  10. 10. 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 end end
  11. 11. Tests documentation and failure messages
  12. 12. When tests output matter - Built-in profiler - Test run filters - Conventions - Built-in matchers
  13. 13. Built-in profiler
  14. 14. 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
  15. 15. Test run filters (Cons.) it "should be true for Fixnum" do 0.should be_true end end
  16. 16. Conventions
  17. 17. 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 < 6 target.should be_between(1, 10)
  18. 18. 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_error lambda {a_call}.should raise_error(<exception> [, message]) lambda {a_call}.should_not raise_error lambda {a_call}.should_not raise_error(<exception> [, message])
  19. 19. 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>).things target.should have_at_least(<number>).things target.should have_at_most(<number>).things target.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)
  20. 20. Problems ? - Hard to learn at the beginning - Routing tests could have more detailed failure messages - Rails upgrade can break tests compatibility
  21. 21. 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)
  22. 22. 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
  23. 23. 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 end end
  24. 24. 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
  25. 25. 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 end end
  26. 26. 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)
  27. 27. Helper specs (Cons.) helper.campaign_attachment_url(campaign, :featured_image). should eql(campaign.featured_image.url) end end
  28. 28. 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>
  29. 29. 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") end end
  30. 30. 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") end end
  31. 31. 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") end End 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") end end
  32. 32. 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") end end
  33. 33. Stubs & Mocks
  34. 34. 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
  35. 35. 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
  36. 36. PeopleJar is using
  37. 37. Are there any problems ? - Writing test is more time consuming - Need to know stubbed library internal implementations - Need to write an integration test first
  38. 38. Stubs in action User.stub(:new) # => nil User.stub(:new).and_return(true) user_object = User.new user_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.paginate User.stub_chain(:active, :paginate).and_return([user_object])
  39. 39. Stubs in action User.stub(:new) # => nil User.stub(:new).and_return(true) user_object = User.new user_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.paginate User.stub_chain(:active, :paginate).and_return([user_object])
  40. 40. 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)
  41. 41. Mocks in action User.should_receive(:new) # => nil User.should_receive(:new).and_return(true) User.should_not_receive(:new) user_object = User.new user_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) # !
  42. 42. Mocks in action (Cons.) user_object.should_receive(:update_attributes).once # default user_object.should_receive(:update_attributes).twice user_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
  43. 43. 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
  44. 44. 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" end end
  45. 45. 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" end end
  46. 46. Question?

×