Post on 30-May-2018
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.html8/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.html8/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/stubble8/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.pdf8/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-book8/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/mocha8/14/2019 Don't Mock Yourself Out
123/126
mocha
http://github.com/floehopper/mocha
http://github.com/floehopper/mochahttp://github.com/floehopper/mocha8/14/2019 Don't Mock Yourself Out
124/126
flexmock
http://github.com/jimweirich/flexmock
http://github.com/floehopper/mochahttp://github.com/floehopper/mocha8/14/2019 Don't Mock Yourself Out
125/126
rr
http://github.com/btakita/rr
http://github.com/floehopper/mochahttp://github.com/floehopper/mocha8/14/2019 Don't Mock Yourself Out
126/126