Don't Mock Yourself Out

download Don't Mock Yourself Out

of 126

Transcript of Don't Mock Yourself Out

  • 8/14/2019 Don't Mock Yourself Out

    1/126

    Dont Mock

    Yourself Out

    David ChelimskyArticulated Man, Inc

  • 8/14/2019 Don't Mock Yourself Out

    2/126

  • 8/14/2019 Don't Mock Yourself Out

    3/126

    http://martinfowler.com/articles/mocksArentStubs.html

    http://martinfowler.com/articles/mocksArentStubs.htmlhttp://martinfowler.com/articles/mocksArentStubs.html
  • 8/14/2019 Don't Mock Yourself Out

    4/126

    Classical and

    Mockist Testing

  • 8/14/2019 Don't Mock Yourself Out

    5/126

    Classicaland

    Mockist Testing

  • 8/14/2019 Don't Mock Yourself Out

    6/126

    Classicaland

    MockistTesting

  • 8/14/2019 Don't Mock Yourself Out

    7/126

    classicist mockist

  • 8/14/2019 Don't Mock Yourself Out

    8/126

  • 8/14/2019 Don't Mock Yourself Out

    9/126

    rs ecist testunitist

  • 8/14/2019 Don't Mock Yourself Out

    10/126

  • 8/14/2019 Don't Mock Yourself Out

    11/126

    istbin ein

    red herring

  • 8/14/2019 Don't Mock Yourself Out

    12/126

    The big issue hereis when to use a

    mock

    http://martinfowler.com/articles/mocksArentStubs.html

    http://martinfowler.com/articles/mocksArentStubs.htmlhttp://martinfowler.com/articles/mocksArentStubs.htmlhttp://martinfowler.com/articles/mocksArentStubs.html
  • 8/14/2019 Don't Mock Yourself Out

    13/126

    agenda

    overview of stubs and mocks

    mocks/stubs applied to rails

    guidelines and pitfalls

    questions

  • 8/14/2019 Don't Mock Yourself Out

    14/126

    test double

  • 8/14/2019 Don't Mock Yourself Out

    15/126

  • 8/14/2019 Don't Mock Yourself Out

    16/126

  • 8/14/2019 Don't Mock Yourself Out

    17/126

    test stub

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    18/126

    test stub

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    19/126

    mock object

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    20/126

    mock object

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    21/126

  • 8/14/2019 Don't Mock Yourself Out

    22/126

    mock object

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    23/126

    mock object

    describeStatementdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    24/126

  • 8/14/2019 Don't Mock Yourself Out

    25/126

  • 8/14/2019 Don't Mock Yourself Out

    26/126

    describeStatementdo

    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

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    27/126

  • 8/14/2019 Don't Mock Yourself Out

    28/126

  • 8/14/2019 Don't Mock Yourself Out

    29/126

    describeStatementdo

    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

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    30/126

  • 8/14/2019 Don't Mock Yourself Out

    31/126

  • 8/14/2019 Don't Mock Yourself Out

    32/126

  • 8/14/2019 Don't Mock Yourself Out

    33/126

    describeStatementdo

    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"

    end

    end

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    34/126

  • 8/14/2019 Don't Mock Yourself Out

    35/126

  • 8/14/2019 Don't Mock Yourself Out

    36/126

    describeStatementdo

    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"

    end

    end

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

    messageexpectation

  • 8/14/2019 Don't Mock Yourself Out

    37/126

  • 8/14/2019 Don't Mock Yourself Out

    38/126

  • 8/14/2019 Don't Mock Yourself Out

    39/126

    describeStatementdo

    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

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    40/126

    describeStatementdo

    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

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    41/126

  • 8/14/2019 Don't Mock Yourself Out

    42/126

    describeStatementdo

    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

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

    ????

  • 8/14/2019 Don't Mock Yourself Out

    43/126

  • 8/14/2019 Don't Mock Yourself Out

    44/126

    describeStatementdo

    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

    class Statement

    defheader

    "Statement for #{@customer.name}"

    end

    end

    bound toimplementation

  • 8/14/2019 Don't Mock Yourself Out

    45/126

  • 8/14/2019 Don't Mock Yourself Out

    46/126

    mocks are often

    used like stubs

  • 8/14/2019 Don't Mock Yourself Out

    47/126

    we verify stubs bychecking state after

    an action

  • 8/14/2019 Don't Mock Yourself Out

    48/126

    we tell mocks to

    verify interactions

  • 8/14/2019 Don't Mock Yourself Out

    49/126

    sometimes stubsjust make the

    system run

  • 8/14/2019 Don't Mock Yourself Out

    50/126

  • 8/14/2019 Don't Mock Yourself Out

    51/126

    isolation from

    non-determinism

    d l

  • 8/14/2019 Don't Mock Yourself Out

    52/126

    random values

    d l

  • 8/14/2019 Don't Mock Yourself Out

    53/126

    random values

    class BoardTest < MiniTest::Unit::TestCase deftest_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

    ti

  • 8/14/2019 Don't Mock Yourself Out

    54/126

    time

    describeEventdo

    it"is not happening before the start time"do

    now =Time.now

    start = now +1

    Time.stub(:now).and_return nowevent =Event.new(:start => start)

    event.should_not be_happening

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    55/126

    isolation from

    external dependencies

    t k

  • 8/14/2019 Don't Mock Yourself Out

    56/126

    network access

    Subject

    Database

    DatabaseInterface

    NetworkInterface

    Internets

    t k

  • 8/14/2019 Don't Mock Yourself Out

    57/126

    network access

    deftest_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

    t k

  • 8/14/2019 Don't Mock Yourself Out

    58/126

    network access

    Subject

    StubDatabase

    StubNetwork

    CodeExample

    t k

  • 8/14/2019 Don't Mock Yourself Out

    59/126

    network access

    deftest_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

  • 8/14/2019 Don't Mock Yourself Out

    60/126

    polymorphic

    collaborators

    t t i

  • 8/14/2019 Don't Mock Yourself Out

    61/126

    strategies

    describeEmployeedo

    it"delegates pay() to payment strategy"do

    payment_strategy = mock()

    employee =Employee.new(payment_strategy)

    payment_strategy.expects(:pay)

    employee.pay

    end

    end

  • 8/14/2019 Don't Mock Yourself Out

    62/126

  • 8/14/2019 Don't Mock Yourself Out

    63/126

    when aremessage expectations

    hel ful?

    side effects

  • 8/14/2019 Don't Mock Yourself Out

    64/126

    side effects

    describeStatementdo

    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

    caching

  • 8/14/2019 Don't Mock Yourself Out

    65/126

    caching

    describeZipCodedo

    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

    interface discovery

  • 8/14/2019 Don't Mock Yourself Out

    66/126

    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

  • 8/14/2019 Don't Mock Yourself Out

    67/126

    isolation testing

  • 8/14/2019 Don't Mock Yourself Out

    68/126

    specifying/testingindividual

    objects in isolation

  • 8/14/2019 Don't Mock Yourself Out

    69/126

    good fit with lots of

    little objects

  • 8/14/2019 Don't Mock Yourself Out

    70/126

  • 8/14/2019 Don't Mock Yourself Out

    71/126

    all of these

    concepts apply tothe non-rails

    specific parts ofour rails apps

  • 8/14/2019 Don't Mock Yourself Out

    72/126

    isolation testing therails-specific parts

    of our applicationss

  • 8/14/2019 Don't Mock Yourself Out

    73/126

  • 8/14/2019 Don't Mock Yourself Out

    74/126

    M

    C

    V

  • 8/14/2019 Don't Mock Yourself Out

    75/126

    B

  • 8/14/2019 Don't Mock Yourself Out

    76/126

    View

    ControllerModel

    Browser

    Router

    Database

  • 8/14/2019 Don't Mock Yourself Out

    77/126

  • 8/14/2019 Don't Mock Yourself Out

    78/126

    rails unit tests

    model classes (repositories)

    model objects

    database

  • 8/14/2019 Don't Mock Yourself Out

    79/126

  • 8/14/2019 Don't Mock Yourself Out

    80/126

  • 8/14/2019 Don't Mock Yourself Out

    81/126

  • 8/14/2019 Don't Mock Yourself Out

    82/126

    rails integration tests

    model classes (repositories)

    model objects

    database

    views

    controllers routing/sessions

  • 8/14/2019 Don't Mock Yourself Out

    83/126

    rails integration tests

    model classes (repositories)

    model objects

    database

    views

    controllers routing/sessions

    !DRY

  • 8/14/2019 Don't Mock Yourself Out

    84/126

    the BDD approach

  • 8/14/2019 Don't Mock Yourself Out

    85/126

    inherited from XP

  • 8/14/2019 Don't Mock Yourself Out

    86/126

    customer s ecs

    develo er s ecs

  • 8/14/2019 Don't Mock Yourself Out

    87/126

    rails integration tests

  • 8/14/2019 Don't Mock Yourself Out

    88/126

    rails integration tests

    + webrat

    shoulda, context,micronaut, etc

  • 8/14/2019 Don't Mock Yourself Out

    89/126

    customer specs areimplemented as

    end to end tests

  • 8/14/2019 Don't Mock Yourself Out

    90/126

    developer specsare implemented as

    isolation tests

  • 8/14/2019 Don't Mock Yourself Out

    91/126

    mocking andstubbing

    with rails

    i l i i

  • 8/14/2019 Don't Mock Yourself Out

    92/126

    partials in view specsdescribe"/registrations/new.html.erb"do

    before(:each) do

    template.stub(:render).with(:partial => anything)

    end

    it"renders the registration navigation"dotemplate.should_receive(:render).with(:partial => 'nav')

    render

    end

    it"renders the registration form "do

    template.should_receive(:render).with(:partial => 'form')

    render

    end

    end

    conditional branches in

  • 8/14/2019 Don't Mock Yourself Out

    93/126

    conditional branches in

    controller specsdescribe"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

  • 8/14/2019 Don't Mock Yourself Out

    94/126

    conditional branches in

  • 8/14/2019 Don't Mock Yourself Out

    95/126

    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

    conditional branches in

    controller specs

    shave a few lines but

  • 8/14/2019 Don't Mock Yourself Out

    96/126

    shave a few lines but

    leave a little stubble

    http://github.com/dchelimsky/stubble

    stubble

    http://github.com/dchelimsky/stubblehttp://github.com/dchelimsky/stubblehttp://github.com/dchelimsky/stubble
  • 8/14/2019 Don't Mock Yourself Out

    97/126

    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

    stubble

  • 8/14/2019 Don't Mock Yourself Out

    98/126

    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

    chains

  • 8/14/2019 Don't Mock Yourself Out

    99/126

    chainsdescribeUsersControllerdo

    it"GET 'best_friend'"domember = 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

  • 8/14/2019 Don't Mock Yourself Out

    100/126

    chains

    describeUsersControllerdo

    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

  • 8/14/2019 Don't Mock Yourself Out

    101/126

    guidlines, pitfalls, andcommon concerns

  • 8/14/2019 Don't Mock Yourself Out

    102/126

    focus on roleshttp://www.jmock.org/oopsla2004.pdf

    Mock Roles, not Objects

    http://www.jmock.org/oopsla2004.pdfhttp://www.jmock.org/oopsla2004.pdf
  • 8/14/2019 Don't Mock Yourself Out

    103/126

    keep things simple

  • 8/14/2019 Don't Mock Yourself Out

    104/126

    avoid tight coupling

  • 8/14/2019 Don't Mock Yourself Out

    105/126

    complex setup is ared flag for design

    issues

  • 8/14/2019 Don't Mock Yourself Out

    106/126

    dont stub/mock the

    object youre testing

  • 8/14/2019 Don't Mock Yourself Out

    107/126

    impedes

    refactoring

  • 8/14/2019 Don't Mock Yourself Out

    108/126

  • 8/14/2019 Don't Mock Yourself Out

    109/126

  • 8/14/2019 Don't Mock Yourself Out

    110/126

  • 8/14/2019 Don't Mock Yourself Out

    111/126

    false positives

  • 8/14/2019 Don't Mock Yourself Out

    112/126

    pdescribeRegistrationsControllerdo

    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

    end

    end

    class RegistrationsController < ApplicationController defpending

    @registrations=Registration.pending

    end

    end

    false positives

  • 8/14/2019 Don't Mock Yourself Out

    113/126

    p

    describeRegistrationdo describe"#pending"do

    it"finds pending registrations"do

    Registration.create!

    Registration.create!(:pending => true)

    Registration.pending.should have(1).item

    end end

    end

    class Registration < ActiveRecord::Base

    named_scope :pending, :conditions => {:pending => true}

    end

    false positives

  • 8/14/2019 Don't Mock Yourself Out

    114/126

    p

    describeRegistrationdo describe"#pending"do

    it"finds pending registrations"do

    Registration.create!

    Registration.create!(:pending => true)

    Registration.pending.should have(1).item

    end end

    end

    class Registration < ActiveRecord::Base

    named_scope :pending, :conditions => {:pending => true}

    end

    false positives

  • 8/14/2019 Don't Mock Yourself Out

    115/126

    p

    describeRegistrationdo describe"#pending"do

    it"finds pending registrations"do

    Registration.create!

    Registration.create!(:pending => true)

    Registration.pending_confirmation.should have(1).item

    end end

    end

    class Registration < ActiveRecord::Base

    named_scope :pending_confirmation, :conditions => {:pending => true}

    end

    false positives

  • 8/14/2019 Don't Mock Yourself Out

    116/126

    p

    describeRegistrationdo describe"#pending"do

    it"finds pending registrations"do

    Registration.create!

    Registration.create!(:pending => true)

    Registration.pending_confirmation.should have(1).item

    end end

    end

    class Registration < ActiveRecord::Base

    named_scope :pending_confirmation, :conditions => {:pending => true}

    end

    cucumber

  • 8/14/2019 Don't Mock Yourself Out

    117/126

  • 8/14/2019 Don't Mock Yourself Out

    118/126

    http://pragprog.com/titles/achbd/the-rspec-book

    http://pragprog.com/titles/achbd/the-rspec-bookhttp://pragprog.com/titles/achbd/the-rspec-book
  • 8/14/2019 Don't Mock Yourself Out

    119/126

    http://www.jmock.org/oopsla2004.pdf http://www.mockobjects.com/book/

    Mock Roles, not Objectsgrowing object-orientedsoftware, guided by tests

    http://xunitpatterns.com/

    http://blog.davidchelimsky.net/

    http://www articulatedman com/

    http://xunitpatterns.com/http://xunitpatterns.com/http://www.mockobjects.com/book/http://www.mockobjects.com/book/http://www.jmock.org/oopsla2004.pdfhttp://www.jmock.org/oopsla2004.pdfhttp://www.articulatedman.com/http://blog.davidchelimsky.net/http://blog.davidchelimsky.net/
  • 8/14/2019 Don't Mock Yourself Out

    120/126

    http://pragprog.com/titles/achbd/the-rspec-book

    http://www.articulatedman.com/

    http://rspec.info/

    http://cukes.info/

    http://cukes.info/http://cukes.info/http://rspec.info/http://rspec.info/http://www.articulatedman.com/http://www.articulatedman.com/http://fit.c2.com/http://fit.c2.com/
  • 8/14/2019 Don't Mock Yourself Out

    121/126

    ruby frameworks

  • 8/14/2019 Don't Mock Yourself Out

    122/126

    rspec-mocks

    http://github.com/dchelimsky/rspec

    http://github.com/floehopper/mochahttp://github.com/floehopper/mocha
  • 8/14/2019 Don't Mock Yourself Out

    123/126

    mocha

    http://github.com/floehopper/mocha

    http://github.com/floehopper/mochahttp://github.com/floehopper/mocha
  • 8/14/2019 Don't Mock Yourself Out

    124/126

    flexmock

    http://github.com/jimweirich/flexmock

    http://github.com/floehopper/mochahttp://github.com/floehopper/mocha
  • 8/14/2019 Don't Mock Yourself Out

    125/126

    rr

    http://github.com/btakita/rr

    http://github.com/floehopper/mochahttp://github.com/floehopper/mocha
  • 8/14/2019 Don't Mock Yourself Out

    126/126