Upload
doannhan
View
216
Download
1
Embed Size (px)
Citation preview
http://martinfowler.com/articles/mocksArentStubs.html
The big issue here is when to use a
mock
http://martinfowler.com/articles/mocksArentStubs.html
agenda๏ overview of stubs and mocks
๏ mocks/stubs applied to rails
๏ guidelines and pitfalls
๏ questions
test stubdescribe 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" endend
test stubdescribe 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" endend
test stubdescribe 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" endend
test stubdescribe 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" endend
mock objectdescribe 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 endend
mock objectdescribe 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 endend
mock objectdescribe 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 endend
mock objectdescribe 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 endend
mock objectdescribe 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 endend
describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
method stubdescribe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
message expectationdescribe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Joe Customer/) statement.print endend
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
messageexpectation
bound to implementation
describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')
statement.header.should == "Statement for Joe Customer" endend
class Statement def header "Statement for #{@customer.name}" endend
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" endend
class Statement def header "Statement for #{@customer.name}" endend
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" endend
class Statement def header "Statement for #{@customer.name}" endend
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" endend
class Statement def header "Statement for #{@customer.name}" endend
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" endend
class Statement def header "Statement for #{@customer.name}" endend
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" endend
class Statement def header "Statement for #{@customer.name}" endend
????
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" endend
class Statement def header "Statement for #{@customer.name}" endend
messageexpectation
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" endend
class Statement def header "Statement for #{@customer.name}" endend
bound to implementation
random valuesclass 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) endend
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 endend
network accessdef 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.finalizeend
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.finalizeend
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 endend
mixins/pluginsdescribe 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 endend
side effectsdescribe 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 endend
cachingdescribe 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? endend
interface discoverydescribe "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 endend
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
rails functional tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
rails functional tests
๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
rails functional tests
!DRY
rails integration tests๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
๏ routing/sessions
rails integration tests๏ model classes (repositories)
๏ model objects
๏ database
๏ views
๏ controllers
๏ routing/sessions
!DRY
partials in view specsdescribe "/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 endend
conditional branches incontroller 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 endend
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 endend
conditional branches incontroller 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 endend
conditional branches incontroller specs
shave a few lines but leave a little stubble
http://github.com/dchelimsky/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 endend
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 endend
stubble
chainsdescribe 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) endend
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) endend
focus on roles
http://www.jmock.org/oopsla2004.pdf
Mock Roles, not Objects
false positivesdescribe RegistrationsController do describe "GET 'pending'" do it "finds the pending registrations" do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end endend
class RegistrationsController < ApplicationController def pending @registrations = Registration.pending endend
false positivesdescribe RegistrationsController do describe "GET 'pending'" do it "finds the pending registrations" do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end endend
class RegistrationsController < ApplicationController def pending @registrations = Registration.pending endend
false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end endend
class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true}end
false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end endend
class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true}end
false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end endend
class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true}end
false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end endend
class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true}end
http://pragprog.com/titles/achbd/the-rspec-book
http://www.jmock.org/oopsla2004.pdf http://www.mockobjects.com/book/
Mock Roles, not Objectsgrowing object-orientedsoftware, guided by tests
http://xunitpatterns.com/
http://pragprog.com/titles/achbd/the-rspec-book
http://blog.davidchelimsky.net/http://www.articulatedman.com/
http://rspec.info/http://cukes.info/
rspec-mocks
http://github.com/dchelimsky/rspec
mocha
http://github.com/floehopper/mocha
flexmock
http://github.com/jimweirich/flexmock
rr
http://github.com/btakita/rr
not a mock
http://github.com/notahat/not_a_mock