~ Hamlet, Act 3 Scene 1, as written by a software engineer
To mock, or not to mock, that is the question:
Whether 'tis nobler in the test to verify
The internal state of real objects,
Or to observe the behavior of fake objects
And by mocking, test them.
Emily Giurleo • RubyConf 2021
To be, or not to be?
Emily Giurleo • RubyConf 2021
Shakespeare
To mock, or not to mock?
Emily Giurleo • RubyConf 2021
Me
Emily Giurleo • RubyConf 2021
How do we know when to use a mock?
(aka Emily Giurleo)
Software Engineer at Numero
Co-founder of WNB.rb
Emily Giurleo • RubyConf 2021
Me
Emily Giurleo • RubyConf 2021
Understand what mocking is
Identify scenarios where mocking improves our tests
Articulate a framework for deciding when to mock
Goals
| | |
Kind of object | | |
What is verified | | |
Classical
Real
State
Mockist
Fake
Behavior
Emily Giurleo • RubyConf 2021
Names and definitions from “Mocks Aren’t Stubs” by Martin Fowler
16th century inventory manager
Warehouse:
keeps track of inventory
Order:
represents customer order
order.fill(warehouse)
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
I use rspec + rspec_mocks
(but you should still watch this talk even if you don’t)
Emily Giurleo • RubyConf 2021
Example adapted from “Mocks Aren’t Stubs” by Martin Fowler
Create a real Warehouse object
Pass the Warehouse to Order#fill
Verify state
Add inventory
Emily Giurleo • RubyConf 2021
Example adapted from “Mocks Aren’t Stubs” by Martin Fowler
Create a FAKE Warehouse object
Define expected behavior
Verify behavior
Emily Giurleo • RubyConf 2021
Which is better?
(that’s a trick question)
Emily Giurleo • RubyConf 2021
This breaks when implementation is changed
Example adapted from “Mocks Aren’t Stubs” by Martin Fowler
Emily Giurleo • RubyConf 2021
Example adapted from “Mocks Aren’t Stubs” by Martin Fowler
Fake object
Fake method
Call fake method on fake object
Check that fake method was called on fake object???
Problems with mocking
Emily Giurleo • RubyConf 2021
Tests are deeply coupled to implementation
Tests become self-referential
Benefits of mocking
Emily Giurleo • RubyConf 2021
To mock, or not to mock?
Emily Giurleo • RubyConf 2021
?
?
?
Fakespeare
Emily Giurleo • RubyConf 2021
#generate_quote
Emily Giurleo • RubyConf 2021
Fakespeare
Shakespeare API
identify nouns (ish)
“Tempt not a desperate man”
replace words
“Tempt not a desperate gem”
“Tempt not a desperate man”
Act I�
Mocking prevents the use of expensive resources
Emily Giurleo • RubyConf 2021
Things that are expensive:
Time
Memory
Money
Emily Giurleo • RubyConf 2021
#generate_quote
Fakespeare
Shakespeare API
identify nouns (ish)
“Tempt not a desperate man”
replace words
“Tempt not a desperate gem”
“Tempt not a desperate man”
Emily Giurleo • RubyConf 2021
Time
Money
Emily Giurleo • RubyConf 2021
Mock this?
Emily Giurleo • RubyConf 2021
Test is coupled to third-party library
Test is coupled to API response shape
Client
Client pattern
Network
request
Rest of gem
Emily Giurleo • RubyConf 2021
Give me a quote
“Tempt not a desperate man”
Same as before
Gem doesn’t interact with Net::HTTP
Keep same response shape even if this changes
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Mock Shakespeare::Client
Force #new method to return mocked client
Define behavior on mocked client
Emily Giurleo • RubyConf 2021
Does your test use expensive resources?
To mock, or not to mock?
?
?
?
Act II�
Mocking creates deterministic tests for non-deterministic code
Emily Giurleo • RubyConf 2021
#generate_quote
Fakespeare
Shakespeare API
identify nouns (ish)
“Tempt not a desperate man”
replace words
“Tempt not a desperate gem”
“Tempt not a desperate man”
Emily Giurleo • RubyConf 2021
x
NetworkError
Client
Client pattern
Network
request
Rest of gem
Emily Giurleo • RubyConf 2021
Give me a quote
Shakespeare::Error
Same as other example
Make client raise an error
Emily Giurleo • RubyConf 2021
Side effects
Emily Giurleo • RubyConf 2021
Inputs that are not passed as arguments; can cause non-deterministic behavior
#generate_quote
Fakespeare
Shakespeare API
identify nouns (ish)
“Tempt not a desperate man”
replace words
“Tempt not a desperate gem”
“Tempt not a desperate man”
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Does your test use expensive resources?
Are you testing a non-deterministic case?
To mock, or not to mock?
?
?
Act III�
Mocking helps test objects whose state can’t/shouldn’t be exposed
Emily Giurleo • RubyConf 2021
Cache
Software layer that temporarily stores data for the purpose of fetching it more quickly
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Cache
RuffleCollarsRUs.com
RuffleCollarsRUs.com
RuffleCollarsRUs.com
Emily Giurleo • RubyConf 2021
Cache
RuffleCollarsRUs.com
RuffleCollarsRUs.com?
RuffleCollarsRUs.com
#generate_quote
Fakespeare
Shakespeare API
identify nouns (ish)
“Tempt not a desperate man”
replace words
“Tempt not a desperate gem”
“Tempt not a desperate man”
Emily Giurleo • RubyConf 2021
x
NetworkError
?
Emily Giurleo • RubyConf 2021
Fakespeare
Shakespeare API
Cache
“Tempt not a desperate man.”
set(“Tempt not a desperate man.”)
Emily Giurleo • RubyConf 2021
Fakespeare
Shakespeare API
Cache
x
“Tempt not a desperate man.”
get
Emily Giurleo • RubyConf 2021
Fakespeare
Shakespeare API
Cache
“Tempt not a desperate man.”
set(“Tempt not a desperate man.”)
Emily Giurleo • RubyConf 2021
The state of a cache is not meant to be examined
Cache
Emily Giurleo • RubyConf 2021
[
“Tempt not a desperate man.”,
“Wisely and slow;
they stumble that run fast.”,
"I will speak daggers to her
but use none.",
]
Fakespeare
Cache
set(“Tempt not a desperate man.”)
Fakespeare
Cache
get
“Tempt not a desperate man.”
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Cache
Emily Giurleo • RubyConf 2021
Execute the real method
Check that the method received the correct argument
Emily Giurleo • RubyConf 2021
Fakespeare
Shakespeare API
Cache
x
“Tempt not a desperate man.”
get
Emily Giurleo • RubyConf 2021
The outcome of using the cache looks the same as the outcome of not using it
Emily Giurleo • RubyConf 2021
Define expected behavior
Verify behavior
Verify that cache is not used
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
Does your test use expensive resources?
Are you testing a non-deterministic case?
Are you testing an object whose state can’t be exposed?
To mock, or not to mock?
?
To mock, or not to mock?
Emily Giurleo • RubyConf 2021
Does your test use expensive resources?
Are you testing a non-deterministic case?
Are you testing an object whose state can’t be exposed?
Mock away!
yes
yes
yes
no
no
no
A pox on your house!
Emily Giurleo • RubyConf 2021
The fault, dear Brutus, is not in our tests, but in our code.
Emily Giurleo
Twitter: @EmilyGiurleo
Emily Giurleo • RubyConf 2021
Website: EmilyGiurleo.dev
Emily Giurleo • RubyConf 2021
https://github.com/egiurleo/fakespeare
Sources
Mocks Aren’t Stubs by Martin Fowler �
Making a Mockery of TDD by Avdi Grimm�
RSpec mocks and stubs in plain English by Jason Swett�
Simplifying Tests by Extracting Side-Effects by Joël Quenneville
Emily Giurleo • RubyConf 2021
Emily Giurleo • RubyConf 2021
twitter.com/wnb_rb
tinyurl.com/join-wnb-rb
WNB.rb
To mock, or not to mock?
Emily Giurleo • RubyConf 2021
Does your test use expensive resources?
Are you testing a non-deterministic case?
Are you testing an object whose state can’t be exposed?
Mock away!
yes
yes
yes
A pox on your house!
no
no
no