UNDERSTANDING
MOCKS
Vaidas Pilkauskas, 2017
@liucijus
Motivation
● Frustration with mocking libraries
● “Mocking is bad?”
● Wasted time debugging tests
● Religious obsession of the tools
Agenda
● Why do we care about mocks
● Problems with Mockito, JMock and other
JVM libraries
● What it takes to write a mocking library
Vaidas Pilkauskas
● Vilnius JUG organizer
● Software developer for more than 15 years
● Coding in Scala for the last 3 years at Wix.com
● Can do fullstack: JavaScript and CSS
● TDD enthusiast
● Mainly interested in software design
● Blog: pilkauskas.lt
● Twitter: @liucijus
Testable Design
Object-oriented design is about managing
dependencies
—Sandi Metz
What is design?
The purpose of design is to allow you to do
design later and its primary goal is to
reduce the cost of change
—Sandi Metz
What is design?
“...gaps between the objects, the
communication protocols”
What is design?
An interface describes whether two
components will fit together, while a
protocol describes whether they will work
together.
—quote in GOOS
What is design?
Testable design
DB
UI Adapter Storage
Adapter
UI
Mocks
E2E/IT IT
Ports and Adapters
Intent
Allow an application to equally be driven by users,
programs, automated test or batch scripts, and to
be developed and tested in isolation from its
eventual run-time devices and databases.
(http://alistair.cockburn.us/Hexagonal+architecture)
https://transubstantiation.wordpress.com/2013/01/02/worlds-most-difficult-reads/
Hexagonal Architecture
Application
MySQL
Application
JavaScript
UI
Application
MySQL
Acceptance
Tests Suite
Application
MySQL
JavaScript
UI
Production
End-to-End
Test Suite
Application
MySQL
JavaScript
UI
Acceptance
Tests Suite
Backup
Service
Application
MySQL
JavaScript
UI
Acceptance
Tests Suite
Production
End-to-End
Test Suite
Mocking
Mockito example
What will this test output?
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Finance finance = mock(Finance.class);
SalesReports reports = new SalesReports(finance);
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
Null pointer!
java.lang.NullPointerException
at mocks.sales.Report.total(SalesReports.java:30)
at mocks.sales.Report.toString(SalesReports.java:51)
at java.lang.String.valueOf(String.java:2994)
http://crunchify.com/what-is-a-difference-between-throw-vs-throws-in-java/null-pointer-exception-crunchify/
Missing stub
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Finance finance = mock(Finance.class);
when(finance.salesFor(date)).thenReturn(sales);
SalesReports reports = new SalesReports(finance);
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
Missing stub spy?
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Finance finance = mock(Finance.class);
when(finance.salesFor(date)).thenReturn(sales);
SalesReports reports = new SalesReports(finance);
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
verify(finance).salesFor(date);
Debugging Mockito changes development
from mock-first into spy-after
JMock example
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Mockery mockery = new Mockery();
Finance finance = mockery.mock(Finance.class);
SalesReports reports = new SalesReports(finance);
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
mockery.assertIsSatisfied();
Mocking interfaces
java.lang.IllegalArgumentException: mocks.sales.Finance is not an
interface
Interface vs. Implementation
● Inversion of control
● Obvious problem: naming
● Most of the time there is only single implementation
● Does our class user care and know if it is an interface or an
implementation?
○ Imaginary interface vs. concrete class
○ Some languages does not have strong types and interfaces
● Works vs. fits together
JMock example
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Mockery mockery = new Mockery();
mockery.setImposteriser(ClassImposteriser.INSTANCE);
Finance finance = mockery.mock(Finance.class);
SalesReports reports = new SalesReports(finance);
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
mockery.assertIsSatisfied();
Unexpected invocation
unexpected invocation
: finance.salesFor(<2017-02-19>)
no expectations specified: did you…
- forget to start an expectation with a cardinality clause?
- call a mocked method to specify the parameter of an
expectation?
what happened before this: nothing!
JMock example
DaySales sales = new DaySales(date, total);
Report expectedReport = new Report(sales);
Mockery mockery = new Mockery();
mockery.setImposteriser(ClassImposteriser.INSTANCE);
Finance finance = mockery.mock(Finance.class);
SalesReports reports = new SalesReports(finance);
mockery.checking(new Expectations() {{
oneOf(finance).salesFor(date); will(returnValue(sales));
}});
Report generatedReport = reports.generateFor(date);
assertEquals(expectedReport, generatedReport);
mockery.assertIsSatisfied();
Problems with JMock
● Breaks (?) Given-When-Then style
● Risk of overspecification
● “Looks like an old product”
● Pick one: verbose or implicit
Comparison of workflows
Mockito
1. Stub
2. Execute
3. Debug nulls
4. Repeat until works
jMock
1. Expect
2. Execute
3. Report expectation
4. Repeat until works
Let’s define what mock is
Mock
● Expectation set before exercising SUT
Spy
● Postmortem look into SUT after it was exercised
A spy and a stub do the same thing. They only differ in
intent. In what follows, you can treat “spy” and “stub” as
synonyms with, I think, no risk of confusion.
— J. B. Rainsberger
Community view on mocking #1
● Tell, don’t ask
● Only mock types you own
● Don’t mock values
Community view on mocking #2
Lenient vs. Strict behaviour
Like Mockito, we firmly believe that a mocking framework should be lenient by
default
—Spock Documentation
JInsist
https://github.com/liucijus/jinsist
Why should someone write a mocking
library?
● Good exercise for a kata
● Learn about problems authors of popular libraries
experience
● See if Java 8 can improve mocking syntax
● Learn how to program proxies with Byte Buddy
Everyone can be taught to sculpt:
Michelangelo would have had to be
taught not to. So it is with great
programmers.
—Alan J. Perlis
Challenge #1
API should be explicit, no surprises
DSL comparison
// Mockito
when(collaborator.aMethod("input")).willReturn("output");
// JMock
mockery.checking(new Expectations() {{
oneOf(collaborator).aMethod("input"); will(returnValue("output"));
}});
// JInsist
mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output");
Mockery mockery = new Mockery();
Collaborator collaborator = mockery.mock(Collaborator.class);
// side effect verification
mockery.expect(collaborator).command(mock -> mock.aMethod("input"));
collaborator.aMethod("input");
mockery.verify(); // verify is optional
Explicit side effect expectation
Explicit query (stub) expectation
Mockery mockery = new Mockery();
Collaborator collaborator = mockery.mock(Collaborator.class);
// stub verification
mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output");
Assert.assertEquals("output", collaborator.aMethod("input"));
mockery.verify(); // verify is optional
Challenge #2
Implement mock creation
How many proxies do we need here?
// mock creates proxy to be used as test double in SUT
Collaborator collaborator = mockery.mock(Collaborator.class);
// we need another one to capture recording of stub
mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output");
Creating proxies with ByteBuddy
● Easy to use library
● Docs are good, but not perfect
● Google, Stack Overflow helps finding
working examples
Create delegating proxy
new ByteBuddy()
.subclass(classToMock)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(delegate)) // handling of method is forwarded
.make()
.load(classToMock.getClassLoader(). ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Intercept proxy calls and handle
@RuntimeType
public Object intercept(
@AllArguments Object[] allArguments,
@Origin Method method
) throws InvocationTargetException, IllegalAccessException {
// do something with method and arguments
// delegator is our own handler
return delegator.handle(method, allArguments);
}
Challenge #3
Reporting invalid stubbing
● Recording stubs
○ Can mock only method calls
foo.doBar() // can be mocked
foo.baz // cannot
○ Report if no mock invocation is recorded
collaboratorMock.command(mock -> {});
jinsist.mock.MethodToStubNotFound: Method not found while stubbing. Make
sure public method is invoked under stubbing.
Challenge #4
Feedback
Should be easy to track down the problem
■ What has happened, and
■ What was expected
JInsist reporting
jinsist.expectations.UnmetExpectations:
Expected: Collaborator.firstMethod(EqualsMatcher first input)
Actual: Collaborator.secondMethod(first input, second input)
What happened before:
Nothing!
Unmet Expectations:
Collaborator.firstMethod(EqualsMatcher first input)
Collaborator.secondMethod(EqualsMatcher first input, EqualsMatcher second input)
Where does it fit?
● Not for legacy testing - use Mockito and if needed
Powermock
● Fits nicely into rigorous TDD and helps guide designs
● Makes clear statement on simplicity, no implicit
concepts
Summary and lessons learned #1
Impressive number of code lines in existing frameworks
Mockito: 79925
JMock: 14410
For comparison
JInsist: 2446
Summary and lessons learned #2
Java 8 features help to define more self contained DSLs:
lambda syntax turns mock setup to call-by-name
Summary and lessons learned #3
Use existing tools, but understand the price
Q & A
@liucijus

Understanding Mocks

  • 1.
  • 2.
    Motivation ● Frustration withmocking libraries ● “Mocking is bad?” ● Wasted time debugging tests ● Religious obsession of the tools
  • 3.
    Agenda ● Why dowe care about mocks ● Problems with Mockito, JMock and other JVM libraries ● What it takes to write a mocking library
  • 4.
    Vaidas Pilkauskas ● VilniusJUG organizer ● Software developer for more than 15 years ● Coding in Scala for the last 3 years at Wix.com ● Can do fullstack: JavaScript and CSS ● TDD enthusiast ● Mainly interested in software design ● Blog: pilkauskas.lt ● Twitter: @liucijus
  • 5.
  • 6.
    Object-oriented design isabout managing dependencies —Sandi Metz What is design?
  • 7.
    The purpose ofdesign is to allow you to do design later and its primary goal is to reduce the cost of change —Sandi Metz What is design?
  • 8.
    “...gaps between theobjects, the communication protocols” What is design?
  • 9.
    An interface describeswhether two components will fit together, while a protocol describes whether they will work together. —quote in GOOS What is design?
  • 10.
    Testable design DB UI AdapterStorage Adapter UI Mocks E2E/IT IT
  • 11.
    Ports and Adapters Intent Allowan application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases. (http://alistair.cockburn.us/Hexagonal+architecture) https://transubstantiation.wordpress.com/2013/01/02/worlds-most-difficult-reads/
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 21.
  • 22.
    What will thistest output? DaySales sales = new DaySales(date, total); Report expectedReport = new Report(sales); Finance finance = mock(Finance.class); SalesReports reports = new SalesReports(finance); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport);
  • 24.
    Null pointer! java.lang.NullPointerException at mocks.sales.Report.total(SalesReports.java:30) atmocks.sales.Report.toString(SalesReports.java:51) at java.lang.String.valueOf(String.java:2994) http://crunchify.com/what-is-a-difference-between-throw-vs-throws-in-java/null-pointer-exception-crunchify/
  • 25.
    Missing stub DaySales sales= new DaySales(date, total); Report expectedReport = new Report(sales); Finance finance = mock(Finance.class); when(finance.salesFor(date)).thenReturn(sales); SalesReports reports = new SalesReports(finance); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport);
  • 26.
    Missing stub spy? DaySalessales = new DaySales(date, total); Report expectedReport = new Report(sales); Finance finance = mock(Finance.class); when(finance.salesFor(date)).thenReturn(sales); SalesReports reports = new SalesReports(finance); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport); verify(finance).salesFor(date);
  • 27.
    Debugging Mockito changesdevelopment from mock-first into spy-after
  • 28.
    JMock example DaySales sales= new DaySales(date, total); Report expectedReport = new Report(sales); Mockery mockery = new Mockery(); Finance finance = mockery.mock(Finance.class); SalesReports reports = new SalesReports(finance); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport); mockery.assertIsSatisfied();
  • 29.
  • 30.
    Interface vs. Implementation ●Inversion of control ● Obvious problem: naming ● Most of the time there is only single implementation ● Does our class user care and know if it is an interface or an implementation? ○ Imaginary interface vs. concrete class ○ Some languages does not have strong types and interfaces ● Works vs. fits together
  • 31.
    JMock example DaySales sales= new DaySales(date, total); Report expectedReport = new Report(sales); Mockery mockery = new Mockery(); mockery.setImposteriser(ClassImposteriser.INSTANCE); Finance finance = mockery.mock(Finance.class); SalesReports reports = new SalesReports(finance); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport); mockery.assertIsSatisfied();
  • 32.
    Unexpected invocation unexpected invocation :finance.salesFor(<2017-02-19>) no expectations specified: did you… - forget to start an expectation with a cardinality clause? - call a mocked method to specify the parameter of an expectation? what happened before this: nothing!
  • 33.
    JMock example DaySales sales= new DaySales(date, total); Report expectedReport = new Report(sales); Mockery mockery = new Mockery(); mockery.setImposteriser(ClassImposteriser.INSTANCE); Finance finance = mockery.mock(Finance.class); SalesReports reports = new SalesReports(finance); mockery.checking(new Expectations() {{ oneOf(finance).salesFor(date); will(returnValue(sales)); }}); Report generatedReport = reports.generateFor(date); assertEquals(expectedReport, generatedReport); mockery.assertIsSatisfied();
  • 34.
    Problems with JMock ●Breaks (?) Given-When-Then style ● Risk of overspecification ● “Looks like an old product” ● Pick one: verbose or implicit
  • 35.
    Comparison of workflows Mockito 1.Stub 2. Execute 3. Debug nulls 4. Repeat until works jMock 1. Expect 2. Execute 3. Report expectation 4. Repeat until works
  • 36.
    Let’s define whatmock is Mock ● Expectation set before exercising SUT Spy ● Postmortem look into SUT after it was exercised
  • 37.
    A spy anda stub do the same thing. They only differ in intent. In what follows, you can treat “spy” and “stub” as synonyms with, I think, no risk of confusion. — J. B. Rainsberger
  • 38.
    Community view onmocking #1 ● Tell, don’t ask ● Only mock types you own ● Don’t mock values
  • 39.
    Community view onmocking #2 Lenient vs. Strict behaviour Like Mockito, we firmly believe that a mocking framework should be lenient by default —Spock Documentation
  • 40.
  • 41.
    Why should someonewrite a mocking library? ● Good exercise for a kata ● Learn about problems authors of popular libraries experience ● See if Java 8 can improve mocking syntax ● Learn how to program proxies with Byte Buddy
  • 42.
    Everyone can betaught to sculpt: Michelangelo would have had to be taught not to. So it is with great programmers. —Alan J. Perlis
  • 43.
    Challenge #1 API shouldbe explicit, no surprises
  • 44.
    DSL comparison // Mockito when(collaborator.aMethod("input")).willReturn("output"); //JMock mockery.checking(new Expectations() {{ oneOf(collaborator).aMethod("input"); will(returnValue("output")); }}); // JInsist mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output");
  • 45.
    Mockery mockery =new Mockery(); Collaborator collaborator = mockery.mock(Collaborator.class); // side effect verification mockery.expect(collaborator).command(mock -> mock.aMethod("input")); collaborator.aMethod("input"); mockery.verify(); // verify is optional Explicit side effect expectation
  • 46.
    Explicit query (stub)expectation Mockery mockery = new Mockery(); Collaborator collaborator = mockery.mock(Collaborator.class); // stub verification mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output"); Assert.assertEquals("output", collaborator.aMethod("input")); mockery.verify(); // verify is optional
  • 47.
  • 48.
    How many proxiesdo we need here? // mock creates proxy to be used as test double in SUT Collaborator collaborator = mockery.mock(Collaborator.class); // we need another one to capture recording of stub mockery.expect(collaborator).query(mock -> mock.aMethod("input")).returns("output");
  • 49.
    Creating proxies withByteBuddy ● Easy to use library ● Docs are good, but not perfect ● Google, Stack Overflow helps finding working examples
  • 50.
    Create delegating proxy newByteBuddy() .subclass(classToMock) .method(ElementMatchers.any()) .intercept(MethodDelegation.to(delegate)) // handling of method is forwarded .make() .load(classToMock.getClassLoader(). ClassLoadingStrategy.Default.WRAPPER) .getLoaded();
  • 51.
    Intercept proxy callsand handle @RuntimeType public Object intercept( @AllArguments Object[] allArguments, @Origin Method method ) throws InvocationTargetException, IllegalAccessException { // do something with method and arguments // delegator is our own handler return delegator.handle(method, allArguments); }
  • 52.
  • 53.
    ● Recording stubs ○Can mock only method calls foo.doBar() // can be mocked foo.baz // cannot ○ Report if no mock invocation is recorded collaboratorMock.command(mock -> {}); jinsist.mock.MethodToStubNotFound: Method not found while stubbing. Make sure public method is invoked under stubbing.
  • 54.
  • 55.
    Should be easyto track down the problem ■ What has happened, and ■ What was expected
  • 56.
    JInsist reporting jinsist.expectations.UnmetExpectations: Expected: Collaborator.firstMethod(EqualsMatcherfirst input) Actual: Collaborator.secondMethod(first input, second input) What happened before: Nothing! Unmet Expectations: Collaborator.firstMethod(EqualsMatcher first input) Collaborator.secondMethod(EqualsMatcher first input, EqualsMatcher second input)
  • 57.
    Where does itfit? ● Not for legacy testing - use Mockito and if needed Powermock ● Fits nicely into rigorous TDD and helps guide designs ● Makes clear statement on simplicity, no implicit concepts
  • 58.
    Summary and lessonslearned #1 Impressive number of code lines in existing frameworks Mockito: 79925 JMock: 14410 For comparison JInsist: 2446
  • 59.
    Summary and lessonslearned #2 Java 8 features help to define more self contained DSLs: lambda syntax turns mock setup to call-by-name
  • 60.
    Summary and lessonslearned #3 Use existing tools, but understand the price
  • 61.