don t mock yourself out
play

Dont Mock Yourself Out David Chelimsky Articulated Man, Inc - PowerPoint PPT Presentation

Dont Mock Yourself Out David Chelimsky Articulated Man, Inc http://martinfowler.com/articles/mocksArentStubs.html Classical and Mockist Testing Classical and Mockist Testing Classical and Mockist Testing classicist mockist merbist


  1. describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == "Statement for Joe Customer" end end message expectation class Statement def header "Statement for #{@customer.name}" end end

  2. describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == "Statement for Joe Customer" end end bound to implementation class Statement def header "Statement for #{@customer.name}" end end

  3. stubs are often used like mocks

  4. mocks are often used like stubs

  5. we verify stubs by checking state after an action

  6. we tell mocks to verify interactions

  7. sometimes stubs just make the system run

  8. when are method stubs helpful?

  9. isolation from non-determinism

  10. random values

  11. random values class BoardTest < MiniTest::Unit::TestCase def test_allows_move_to_last_square board = Board.new( :squares => 50, :die => MiniTest::Mock.new.expect('roll', 2) ) piece = Piece.new board.place(piece, 48) board.move(piece) assert board.squares[48].contains?(piece) end end

  12. time describe Event do it "is not happening before the start time" do now = Time.now start = now + 1 Time.stub(:now).and_return now event = Event.new(:start => start) event.should_not be_happening end end

  13. isolation from external dependencies

  14. network access Database Interface Database Subject Network Internets Interface

  15. network access def test_successful_purchase_sends_shipping_message ActiveMerchant::Billing::Base.mode = :test gateway = ActiveMerchant::Billing::TrustCommerceGateway.new( :login => 'TestMerchant', :password => 'password' ) item = stub() messenger = mock() messenger.expects(:ship).with(item) purchase = Purchase.new(gateway, item, credit_card, messenger) purchase.finalize end

  16. network access Stub Database Code Subject Example Stub Network

  17. network access def test_successful_purchase_sends_shipping_message gateway = stub() gateway.stubs(:authorize).returns( ActiveMerchant::Billing::Response.new(true, "ignore") ) item = stub() messenger = mock() messenger.expects(:ship).with(item) purchase = Purchase.new(gateway, item, credit_card, messenger) purchase.finalize end

  18. polymorphic collaborators

  19. strategies describe Employee do it "delegates pay() to payment strategy" do payment_strategy = mock() employee = Employee.new(payment_strategy) payment_strategy.expects(:pay) employee.pay end end

  20. mixins/plugins describe AgeIdentifiable do describe "#can_vote?" do it "raises if including does not respond to birthdate" do object = Object.new object.extend AgeIdentifiable expect { object.can_vote? }.to raise_error( /must supply a birthdate/ ) end it "returns true if birthdate == 18 years ago" do object = Object.new stub(object).birthdate {18.years.ago.to_date} object.extend AgeIdentifiable object.can_vote?.should be(true) end end end

  21. when are message expectations helpful?

  22. side effects describe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end

  23. caching describe ZipCode do it "should only validate once" do validator = mock() zipcode = ZipCode.new("02134", validator) validator.should_receive(:valid?).with("02134").once. and_return(true) zipcode.valid? zipcode.valid? end end

  24. interface discovery describe "thing I'm working on" do it "does something with some assistance" do thing_i_need = mock() thing_i_am_working_on = ThingIAmWorkingOn.new(thing_i_need) thing_i_need.should_receive(:help_me).and_return('what I need') thing_i_am_working_on.do_something_complicated end end

  25. isolation testing

  26. specifying/testing individual objects in isolation

  27. good fit with lots of little objects

  28. all of these concepts apply to the non-rails specific parts of our rails apps

  29. isolation testing the rails-specific parts of our applicationss

  30. V M C

  31. View Controller Model

  32. Browser Router View Controller Model Database

  33. rails testing ๏ unit tests ๏ functional tests ๏ integration tests

  34. rails unit tests ๏ model classes (repositories) ๏ model objects ๏ database

  35. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers

  36. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers

  37. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database !DRY ๏ views ๏ controllers

  38. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers ๏ routing/sessions

  39. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database !DRY ๏ views ๏ controllers ๏ routing/sessions

  40. the BDD approach

  41. inherited from XP

  42. customer specs developer specs

  43. rails integration tests + webrat shoulda, context, micronaut, etc

  44. customer specs are implemented as end to end tests

  45. developer specs are implemented as isolation tests

  46. mocking and stubbing with rails

  47. partials in view specs describe "/registrations/new.html.erb" do before(:each) do template.stub(:render).with(:partial => anything) end it "renders the registration navigation" do template.should_receive(:render).with(:partial => 'nav') render end it "renders the registration form " do template.should_receive(:render).with(:partial => 'form') render end end

  48. conditional branches in controller specs describe "POST create" do describe "with valid attributes" do it "redirects to list of registrations" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_return(true) post 'create' response.should redirect_to(registrations_path) end end end

  49. conditional branches in controller specs describe "POST create" do describe "with invalid attributes" do it "re-renders the new form" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_raise( ActiveRecord::RecordInvalid.new(registration)) post 'create' response.should render_template('new') end end end

  50. conditional branches in controller specs describe "POST create" do describe "with invalid attributes" do it "assigns the registration" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_raise( ActiveRecord::RecordInvalid.new(registration)) post 'create' assigns[:registration].should equal(registration) end end end

  51. shave a few lines but leave a little stubble http://github.com/dchelimsky/stubble

  52. stubble describe "POST create" do describe "with valid attributes" do it "redirects to list of registrations" do stubbing(Registration) do post 'create' response.should redirect_to(registrations_path) end end end end

  53. stubble describe "POST create" do describe "with invalid attributes" do it "re-renders the new form" do stubbing(Registration, :as => :invalid) do post 'create' response.should render_template('new') end end it "assigns the registration" do stubbing(Registration, :as => :invalid) do |registration| post 'create' assigns[:registration].should equal(registration) end end end end

  54. chains describe UsersController do it "GET 'best_friend'" do member = stub_model(User) friends = stub() friend = stub_model(User) User.stub(:find).and_return(member) member.stub(:friends).and_return(friends) friends.stub(:favorite).and_return(friend) get :best_friend, :id => '37' assigns[:friend].should equal(friend) end end

  55. chains describe UsersController do it "GET 'best_friend'" do friend = stub_model(User) User.stub_chain(:find, :friends, :favorite). and_return(friend) get :best_friend, :id => '37' assigns[:friend].should equal(friend) end end

Recommend


More recommend