Thermostat example demonstrates dependency injection principles. Initially, the naive Thermostat class directly creates its dependencies like the TemperatureSensor class. This leads to tight coupling. To decouple the classes, an interface TemperatureSensor is extracted. The dependency is then injected via the constructor rather than created internally. This allows injecting different sensor implementations and makes the Thermostat class more reusable and testable.
9. Web of Objects
● Communication patterns are set of rules
● Rules govern roles they play, what messages they
can send and when
● Communication patterns gives meaning to possible
relationships among objects
10. Web of Objects
● Either the object returns value to the caller
● Either the object changes it’s state
● Either it interacts with other object via message
passing
● Makes an external call that changes state of that
system such as database call, web service call etc.
12. Web of Objects
● set.add(val)
○ Adds value to the set (change of state)
○ Returns boolean (message passing)
● list.clear()
○ Clears all elements (change of state)
○ Doesn’t return any value (no message passing)
○ Need to call additional methods to verify
behavior eg. size(), isEmpty()
13. Web of Objects
● observable.notifyObservers()
○ Neither returns any value (no message
passing)
○ Nor does it change any state
○ It interacts with other objects/observers and
passes message to those objects
14. Testing Units in Isolation
TEST
TEST
?
?
?
?
Mocks
Seam
TEST
Real
Unit under test
Internal
Structure
19. State Verification
ArrayList<Integer> list = new ArrayList<>();
// Exercise the system under test
list.add(1);
// State verification - verify list is no
more empty
assertFalse(list.isEmpty());
20. State Verification
// Create a new thread
Thread t = new Thread(() -> {});
// State verification - Verify it is in NEW
state
assertThat(t.getState(),
is(equalTo(Thread.State.NEW)));
22. Behavior Verification
● How to test if SUT does not have any state?
● How to test if SUT does not change state?
23. Behavior Verification
When SUT does not change state or return value or
cause any visible side effect, it MUST interact with
other object or component.
We can test that interaction!
25. ● Places where we can alter system flow
● Essentially polymorphism (ideally interfaces)
● Allow us to isolate SUT when unit testing
● Procedural code (static) has no seams
● Difficult to test procedural code
SEAM
Not all procedural code is bad! Eg. static utilities like
Math.abs(), Math.max() etc.
26. Behavior Verification
WeatherObservable observable = new WeatherObservable();
WeatherObserver observer = new WeatherObserver();
observable.addObserver(observer);
// exercise the SUT
observable.setWeather("FALL");
// Verify that observer was notified
// How to verify that?
29. Manual Behavior Verification
// A test specific observer that tracks if method
was called
class TestObserver implements Observer {
private boolean called = false;
@Override
public void update(Observable o, Object arg) {
this.called = true;
}
}
30. Manual Behavior Verification
WeatherObservable observable = new WeatherObservable();
// Custom hand rolled observer
TestObserver observer = new TestObserver();
observable.addObserver(observer);
// exercise the SUT
observable.setWeather("FALL");
// Manual behavior verification
assertTrue(observer.called);
31. Manual Behavior Verification
How to test the following:
● Observer should only be called once
● Observer should be called with exact argument
32. Manual Behavior Verification
// A test specific observer that tracks if method
was called
class TestObserver implements Observer {
private int count = 0;
private Object arg;
@Override
public void update(Observable o, Object arg) {
this.count++;
this.arg = arg;
}
}
34. Manual Behavior Verification
How to test the following:
● Any one of multiple methods should be called
● Different methods called with different arguments
● Observers should be called in exact order in which
they were added
● Many more advanced verifications..
35. Manual Behavior Verification
We are already starting to add logic in test code
itself!! So there is a possibility that test code has
bugs.
Me: So now should we write test cases for test
code?!
Your reaction: That ain’t happening!
37. Behavior Verification Strategies
1. Complicates test logic
2. Need to repeat it for
all classes
3. Time consuming and
error prone
4. Test readability
suffers
MOCKING
LIBRARY
40. Stubbing
● Providing hard-coded
answers/behaviors from
methods called during test
● Test case wise
answer/behavior can be
changed
● Stubbing logic MUST be
simple
For instance:
● Returning a test/dummy user
from DAO layer
● Throwing exception from
DAO layer to replicate error
scenario
a.k.a Training phase
41. Exp. Verification
● Verify how the SUT should
have interacted with its
collaborators
● Verify the communication
contract between SUT and its
collaborators
● MUST be present in every
test case
For instance:
● Verify Observer#update()
method was called once only
● Verify argument was
matching
● Verify Observers were called
in order they were added
a.k.a assertion phase
42. Mocks
Stubs
Stubbing + Verification
Hard-coded results
No verification
Fakes
Working but simplified version of
production code, like DAO using
collection rather than DB
Dummy
Objects passed around but never
actually used
43. Fake
a working simplified version
LoginService
AccountDAO
FakeAccountDAO HashMap
Fake
implementation
login(user)
getPasswordHash()
DB
44. Stub
object trained with predefined data
GradesService Gradebook
averageGrades(student)
DB
getGrades(student)
OOP: 8
FP: 6
DB: 10
Stub of grades
system
45. Mock
objects that register calls they receive
SecurityCentral
DoorMock
WindowMock
securityOn()
close()
close()
Action to verify
48. Mockito code samples are given only for learning
purposes and don’t necessarily reflect good
practices. We will mock Java library classes for
learning purposes but it is not recommended to
mock third party classes.
Warning
50. Creating Mock
// Rule will automatically init mocks which have
annotation
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private List<String> mockedList;
@Test
public void creatingAMockUsingRule() {
// Mock will already be created for use
assertThat(mockedList, is(notNullValue()));
}
Using rule
51. Verification
@Test
public void simpleVerification() {
// perform some interactions, mock remembers all
mockedList.add("one");
mockedList.clear();
// Verification of interactions, selectively
Mockito.verify(mockedList).add("one");
Mockito.verify(mockedList).clear();
}
52. Stubbing
@Test
public void simpleStubbingReturnValueFromMethod() {
// Stubbing or training the mock
Mockito.when(mockedList.get(0)).thenReturn("one");
// Using the stubbed mock
assertThat(mockedList.get(0), is(equalTo("one")));
// Because we have not trained the mock to expect
argument 1
assertThat(mockedList.get(1), is(nullValue()));
}
Returning value from method
53. Stubbing
@Test
public void stubbingExceptionFromMethod() {
// Train mocked list to throw exception when called
with argument 1
Mockito.when(mockedList.get(1)).thenThrow(
new RuntimeException("Stubbing exception"));
exception.expect(RuntimeException.class);
exception.expectMessage("Stubbing exception");
mockedList.get(1);
}
Throwing exception from method
59. Verification
@Test
public void verifyInorderInvocationWithMultipleMocks() {
firstMock.add("first");
secondMock.add("second");
thirdMock.add("third");
// Not necessary to include all mocks
InOrder inOrder = Mockito.inOrder(firstMock, thirdMock);
inOrder.verify(firstMock).add("first");
inOrder.verify(thirdMock).add("third");
}
In order invocations (multiple mocks)
63. Behavior Verification Strategies
compared
1. Complicates test logic
2. Need to repeat it for
all classes
3. Time consuming and
error prone
4. Test readability
suffers
1. No test logic
2. No repetition
3. Only time consumed
is to learn the library
4. Test readability
greatly improved
69. Thorough
Test for scenarios not coverage (please!!!)
Test business logic not setters/getters
Exceptional scenarios such as DB
connectivity failure, timeout, invalid input
71. Thermostat Example
Please refer to source code for Thermostat1
As a factory inspector, I want a thermostat, so that I can set
target temperature for my machines and log on console if
temperature fluctuates to too high or low levels
Requirement
72. Alert
Log alert on
console
Detect temperature
using Chip121
hardware
Naive Example
<<creates>>
<<creates>>
Thermostat
Please refer to source code for Thermostat1
Chip121TemperatureSensor
Log alerts when
temperature
fluctuates away
from target
temperature
73. Naive Example
Please refer to source code for Thermostat1
public Thermostat1(double targetTemperature) {
this.targetTemperature = targetTemperature;
// Taking decision of type of sensor by itself
this.sensor = new Chip121TemperatureSensor();
this.alert = new Alert();
}
74. Thermostat Example
Please refer to source code for Thermostat1
As a factory inspector, I want a thermostat, so that I can set
target temperature for my machines using sensor and log
on console if temperature fluctuates to too high or low
levels
Requirement
75. Thermostat Example
Please refer to source code for Thermostat1
As a thermostat manufacturer, I want the thermostat to be
able to use different sensors because some newer
thermostat models are better at sensing temperature.
Requirement
76. Naive Example
Please refer to source code for Thermostat1
Currently we can’t reuse because
● Thermostat1 creates sensor from constructor
leaving us helpless if we wan’t to choose other sensor
● There is no interface for sensor
77. Naive Example
Please refer to source code for Thermostat1
Alert
Log alert on
console
Detect temperature
using Chip121
hardware
<<creates>>
<<creates>>
Thermostat Chip121TemperatureSensor
Directly creates
concrete class
Log alerts when
temperature
fluctuates away
from target
temperature
78. Extract Interface
Detect temperature
using Chip121
hardware
Chip121TemperatureSensor
Please refer to source code for Thermostat5
Detect temperature
using some hardware
<<I>> TemperatureSensor
Detect temperature
using XYZ hardware
XYZTemperatureSensor
79. Use interface
Please refer to source code for Thermostat5
Detect temperature
using some hardware
<<creates>>
Thermostat <<I>> TemperatureSensor
Log alerts when
temperature
fluctuates away
from target
temperature
Directly creates
concrete class
Chip121TemperatureSensor XYZTemperatureSensor
Interface is defined
80. Dependency Injection
Please refer to source code for Thermostat5
Detect temperature
using some hardware
<<depends>>
Thermostat <<I>> TemperatureSensor
Log alerts when
temperature
fluctuates away
from target
temperature
Chip121TemperatureSensor XYZTemperatureSensor
Interface is defined and
dependency is injected
via constructor
81. Dependency Injection
Please refer to source code for Thermostat5
// Dependency
private final TemperatureSensor sensor;
Thermostat2(double targetTemperature,
TemperatureSensor sensor) {
this.targetTemperature = targetTemperature;
this.sensor = sensor;
...
}
82. Dependency Injection
When constructing objects, the act of connecting
objects with other objects, or “injecting” objects into
other objects. It is the “D” in S.O.L.I.D principles.
● Constructor Injection (preferred way)
● Field Injection (way frameworks do it using
annotation)
● Method injection (using setters)
84. Thermostat Example
Please refer to source code for Thermostat1
As a thermostat manufacturer, I want the thermostat to be
able to use different sensors because some newer
thermostat models are better at sensing temperature.
Requirement
85. Naive Example
Please refer to source code for Thermostat1
As a air conditioner manufacturer, I want to control
compressor and heater using thermostat, so that I can
maintain room temperature by switching on/off
heater/compressor
Requirement
86. Naive Example
Please refer to source code for Thermostat1
Currently we can’t reuse because
● Thermostat2 creates Alert which can only log to
console
● There is no interface for alert mechanism
87. if (tooCold(temperature)) {
alert.temperatureTooLow(temperature);
}
Naive Example
Please refer to source code for Thermostat1
Here alert is not a dependency. But rather a notification that it
needs to send to some other objects. Just like Observer pattern.
Observer entities will react to that notification and apply custom
business logic like logging to console, controlling compressor or
heater
88. Please refer to source code for Thermostat1
Extract Interface
Log temperature
fluctuations to console
ConsoleAlert
Callback to receive
temperature
fluctuation events
Switch on/off controller
based on temperature
fluctuation
CompressorController
<<I>> TemperatureListener
...
89. Please refer to source code for Thermostat1
Use Interface
<<creates>>
Thermostat <<I>> TemperatureListener
Log alerts when
temperature
fluctuates away
from target
temperature
Directly creates
concrete class
ConsoleAlert CompressorController
Interface is defined
Callback to receive
temperature
fluctuation events
90. Please refer to source code for Thermostat1
Open for extension
<<notifies>>
Thermostat <<I>> TemperatureListener
Notifies when
temperature
fluctuates away
from target
temperature
ConsoleAlert CompressorController
Interface is defined &
multiple receivers
Callback to receive
temperature
fluctuation events
*
1
...
91. Refactored Naive
Example
Please refer to source code for Thermostat5
<<depends>>
Thermostat
<<I>>
TemperatureSensor
Notifies when
temperature
fluctuates away
from target
temperature
Chip121... XYZ...
<<I>>
TemperatureListener
Console... Compressor...
<<notifies>>
1
*
...
...
Seam
92. Refactored Naive
Example
Please refer to source code for Thermostat1
Thermostat3 thermostat5 = new Thermostat3(24,
temperatureSensor);
thermostat5.addListener(new HeaterController());
thermostat5.addListener(new CompressorController());
93. Thermostat Example
Please refer to source code for Thermostat1
As a air conditioner manufacturer, I want to control
compressor and heater using thermostat, so that I can
maintain room temperature by switching on/off
heater/compressor
Requirement
94. TESTS
are first client of your code
Assume previous requirements were given by Unit tests
95. Naive Example
Please refer to source code for Thermostat1
As a unit test
1) I want to use mock sensor as actual chipset cannot be
used in unit testing
2) use mock listener as heater or compressor will not be
available in unit testing and mocking sysout is not a
good option
Requirement
97. Refactored Naive
Example
Please refer to source code for Thermostat1
@Test
public void generatesTooHotAlertIfTempratureExceedsTargetTemperatureThreshold()
{
when(mockSensor.getReading()).thenReturn(24.11);
thermostat.addListener(mockListener);
...
verify(mockListener).onTemperatureTooHigh(24.11);
}
98. Observations
● Unit testing makes code loosely coupled
● If we started with the goal of unit testing then our code naturally
becomes reusable
● Isolating System Under Test requires Seam
● Improves design of your units (not always)
99. Thermostat Example
Please refer to source code for Thermostat1
As a unit test
1) I want to use mock sensor as actual chipset cannot be
used in unit testing
2) use mock listener as heater or compressor will not be
available in unit testing and mocking sysout is not a
good option
Requirement
101. Naive Example
Please refer to source code for Thermostat1
As a lucky game player, I want a game of dice so that I can
play the game to test my luck and win rewards.
We will roll dice thrice and if dice has face value 6 all th
then user will get reward.
Requirement
102. Randomness Example
Please refer to source code for DiceOfFortuneGame
public class DiceGame {
private Random random = new Random();
/**
* Rolls dice three times and returns result. Game can be won if dice value
is equal to 6.
*/
public boolean rollDice() {
return isLucky() && isLucky()
&& isLucky();
}
private boolean isLucky() {
return roll() == 6;
}
}
private int roll() {
return random.ints(1, 7)
.findFirst().getAsInt();
}
104. Randomness Example
Please refer to source code for DiceOfFortuneGame
public class DiceGameTest {
private DiceGame game = new DiceGame();
// Coverage yes but no good as no assertion
@Test
public void testNoException() {
boolean unpredictableResult = game.rollDice();
// We cannot predict whether game is working or not
because it is using random value and we cannot control it.
System.out.println(unpredictableResult);
}
}
106. Refactored Randomness Example
public class DiceGame {
private Random random;
DiceGame(Random random) {
this.random = random;
}
public boolean rollDice() {
return isLucky() && isLucky() && isLucky();
}
private boolean isLucky() {
return roll() == 6;
}
}
Please refer to source code for DiceOfFortuneGame
107. Refactored Randomness Example
Please refer to source code for DiceOfFortuneGame
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Random mockRandom;
@InjectMocks
private DiceGame game;
@Test
public void gameIsWonIfThreeConsecutiveRollsOfDiceFaceShows6() {
Mockito.when(mockRandom.ints(Mockito.anyInt(), Mockito.anyInt())
.findFirst()).thenReturn(of(6));
boolean result = game.rollDice();
assertTrue(result);
verify(mockRandom.ints(1, 7),
times(3)).findFirst();
}
108. Randomness Example
Please refer to source code for Thermostat1
As a lucky game player, I want a game of dice so that I can
play the game to test my luck and win rewards.
We will roll dice once and if dice face has value greater
than 3 then user will get reward.
Requirement
110. Randomness Example
Please refer to source code for Thermostat1
class Chip121TemperatureSensor {
// Creating random makes tests unpredictable
private Random random = new Random();
private double getReading() {
return random.doubles(targetTemperature - 1.0,
targetTemperature + 1.0).findFirst().getAsDouble();
}
}
111. Randomness Example
Please refer to source code for DiceOfFortuneGame
Constructor Injection of Random
If we mock Random, we violate
○ Don’t mock classes you don’t own
■ Ugly to mock Connection, PreparedStatement
○ “Mock interfaces only” ( unless testing legacy code)
112. Please refer to source code for Thermostat1
Extract Interface
Provide Random value
using java library
SystemRandomProvider
Source of
unpredictable random
value
Provide fixed value on
each call
(for testing only)
FixedRandomProvider
<<I>> RandomProvider
...
113. Random
Provider
Source of
unpredictable
random value
Refactored Randomness Example
<<depends>>
Please refer to source code for Thermostat1
Detect temperature
using Chip121
hardware
Chip121Temperature Sensor
FixedRandom
Provider
SystemRandom
Provider
Seam
114. Refactored Randomness Example
Please refer to source code for Thermostat1
class Chip121TemperatureSensor {
private RandomProvider randomProvider;
private double getReading() {
return randomProvider.nextDouble(targetTemperature - 1.0,
targetTemperature + 1.0);
}
}
115. Refactored Randomness Example
Please refer to source code for Thermostat1
@Test
public void
sensorGeneratesTemperatureWithinPlusOrMinusOneRangeOfTargetTemperature() {
sensor.getReading();
// Tests are incomplete without this assertion. Why?
verify(fixedRandomProvider, times(1)).nextDouble(TARGET_TEMPERATURE - 1.0,
TARGET_TEMPERATURE + 1.0);
}
@Test
public void returnsTheCurrentTemperatureReading() {
fixedRandomProvider.setRandom(24);
assertThat(sensor.getReading(), is(equalTo(24.0)));
}
117. Read Sensor data
Every Seconds
Thread Example
<<creates>>
Thread.sleep(1000)
Thermostat
Please refer to source code for Thermostat1
Thread
Log alerts when
temperature
fluctuates away
from target
temperature
<<call>>
118. public Thermostat3(double targetTemperature) {
...
// Creates thread which makes it difficult to test
new Thread(() -> {
while (!Thread.interrupted()) {
sleep();
setCurrentTemperatureReading(sensor.getReading());
}
}).start();
}
Thread Example
Please refer to source code for Thermostat1
119. @Test
public void test() throws InterruptedException {
Thermostat3 thermostat1 = new Thermostat3(24);
Thread.sleep(2000); // Wait till first reading is taken
double temperature = thermostat1.getCurrentTemperature();
...
}
Thread Example
Please refer to source code for Thermostat1
Caution: Slow Tests!!
120. Thread Example
Please refer to source code for Thermostat1
Currently Thermostat is hard to test
● Thermostat creates thread in constructor
● Test case is hard to debug as sensor reading performed
in separate Thread
How make Thermostat testable??
121. Please refer to source code for Thermostat1
Thread Example
Run Task in separate
Thread using Java
Executor API
DefaultTaskScheduler
Run the task at regular
interval
Provide control over
scheduled task by
capturing the task
FakeTaskScheduler
<<I>> TaskScheduler
...
122. Task Scheduler
Run the task at
regular interval
Refactored Thread Example
<<depends>>
Please refer to source code for Thermostat1
Schedule the task
which read sensor
data every 1 sec
Thermostat
DefaultTaskSched
uler
FakeTaskSched
uler
Seam
123. Refactored Thread Example
Please refer to source code for Thermostat1
Thermostat4(double targetTemperature, RandomProvider
randomProvider, TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
// Does not manage thread itself but depends on task
scheduler to do the job
taskScheduler.scheduleFixedDelayTask(() ->
setCurrentTemperatureReading(sensor.getReading()),
1, TimeUnit.SECONDS);
}
124. Refactored Thread Example
Please refer to source code for Thermostat1
@Test
public void generatesTooHotAlertIfTempratureExceedsTargetTemperatureThreshold(){
when(mockSensor.getReading()).thenReturn(24.11);
// Run the scheduled task in sync (current thread only)
fakeTaskScheduler.tick();
verify(mockListener).onTemperatureTooHigh(24.11);
}
126. @Test
public void test1() {
new DreadfulController().doSomething();
}
@Test
public void test2() {
new DreadfulController().doSomething();
}
Will these tests pass?
127. @Test
public void thisTestMayFail() {
new DreadfulController().doSomething();
}
@Test
public void thisTestMayFailToo() {
new DreadfulController().doSomething();
}
Any test can fail
randomly
Why?
128. @Test
public void thisTestMayFail() {
new DreadfulController().doSomething();
}
@Test
public void thisTestMayFailToo() {
new DreadfulController().doSomething();
}
Any test can fail
randomly
Hidden static
static
variable
130. Hidden Global State
public class DreadfulHelper {
private static int globalEvilState = 0;
public static void doDreadfulStuff() {
if (++globalEvilState % 2 == 0) {
throw new RuntimeException("Global evil
hidden state will haunt you forever!");
}
}
}
132. Solution: Global State
You control the state not the class
DreadfulController
calls
PleasantHelper
calls
instance
variable
uses
Use same instance of
dependency in
production and
different in test
DreadfulController
PleasantController PleasantDependency
133. Solution: Global State
Test code
@Before
public void createController() {
//Fresh clean copy of PleasantHelper for each test
controller = new PleasantController(new PleasantDependency(
new PleasantHelper()));
}
@Test
public void thisTestCannotFailAsItIsIsolated() {
controller.doSomething();
}
@Test
public void thisTestCannotFailAsItIsIsolatedToo() {
controller.doSomething();
}
134. Solution: Global State
Production code
PleasantHelper sharedHelper = new PleasantHelper();
PleasantDependency pleasantDependency
= new PleasantDependency(sharedHelper);
PleasantController controller1
= new PleasantController(pleasantDependency);
PleasantController controller2
= new PleasantController(pleasantDependency);
controller1.doSomething();
// will throw exception because of shared PleasantHelper
controller2.doSomething();
135. Solution: Global State
Controlled shared state
● Don’t enforce global state by using static variables
● Use shared state to mimic global state
● Responsibility of maintaining shared state is on the
objects that create and weave objects together like
Factories or Mother classes
136. ● Unpredictable test results (flaky)
○ Reduces confidence in tests
● Test order affects test results
○ Some test may change state which affects other test
● Issues in running tests in parallel
○ Different test threads affect same state
Global State Issues
138. Singleton example
BadUserController
Processes user
request and
alerts if
processing
takes time
SNMPService ConfigurationManager
snmp-servic
e.xml
snmp-servic
e.xml
snmp-servic
e.xml
Socket
<<depends>>
static call to
getInstance()
Singleton Singleton
<<opens>> <<reads>>
External world
static call to
getInstance()
139. Singleton example
BadUserController
Processes user
request and
alerts if
processing
takes time
SNMPService ConfigurationManager
snmp-servic
e.xml
snmp-servic
e.xml
snmp-servic
e.xml
Socket
<<depends>>
static call to
getInstance()
Singleton Singleton
<<opens>> <<reads>>
External world
static call to
getInstance()
Unintentional baggage
Unintentional
baggage
140. Singleton example
An ugly attempt to test
UserRequest userRequest = newUserRequest();
User user = new User();
user.setFirstName(TEST_USER_NAME);
user.setId(userRequest.getUserId());
...
ConfigurationManager.getInstance().init();
// Tries to establish connection to SNMP service
SNMPService.getInstance().init();
controller.processUserRequest(userRequest);
// Can't really test this now because its tightly coupled
with SNMP
141. Singleton = Global State
● Uses a static variable hence global state (isolation issue)
● Can only be accessed via static method (Seam issue)
● Not all singletons are evil, we need singletons but not as
frequently as we use
● Don’t use Singleton only to increase scope of object, so that
anywhere in the code we can use it
Makes code difficult to test
142. Singleton example
if (TimeUnit.MILLISECONDS.toSeconds(end - start) > 1) {
SNMPService.getInstance().generateAlert("User lookup
operation took more than 1 sec");
}
● Creating SNMP connection is not feasible in unit test
● Static call means procedural code
● No Seam available to test
How to solve this?
143. Singleton example
Inject Singleton
BadUserController
Processes user
request and
alerts if
processing
takes time
SNMPService ConfigurationManager
snmp-servic
e.xml
snmp-servic
e.xml
snmp-servic
e.xml
Socket
<<depends>>
Singleton Singleton
<<opens>> <<reads>>
External world
static call to
getInstance()
Real
implementation
MockSNMPServic
e
Constructor
Injection
Constructor
Injection
Test code
144. Refactored Singleton example
<<depends>>
<<I>> AlertService
SNMPService MockAlertService
Interface is defined &
dependency injection
Service that can
generate alert
1
...
GoodUserController
Processes user
request and
alerts if
processing
takes time
1
Test code uses
145. Refactored Singleton example
Hows tests should look like
controller = new GoodUserController(.., mockAlertService, ..);
… Some more test training code here
// Exercise SUT
controller.processUserRequest(userRequest);
verify(mockAlertService).generateAlert("User lookup operation
took more than 1 sec");
147. Time dependency example
long start = System.nanoTime();
User user = userRepository.getUser(request.getUserId());
long end = System.nanoTime();
if (TimeUnit.MILLISECONDS.toSeconds(end - start) > 1) {
// Generate alert
}
How to test alert scenario?
148. Time dependency example
Mockito.doAnswer(invocationOnMock -> {
// Introduce real delay, make tests slower! Yayy
Thread.sleep(2000);
return user;
}).when(mockHibernateRepository)
.getUser(Mockito.anyString());
A naive attempt - Thread.sleep()
149. Introducing real delay with Thread.sleep() makes tests slower
Time dependency example
Problems with naive attempt
Few tests with
Thread.sleep()
Ain’t that bad!
Add a few more to
the mix
Tests take 4-5 mins
Becomes a habit!
Well it’s all right.
Tests take 20 mins
Few sprints later
Tests take hours!
Why is build so
slow?
Dev: “Tests take too
long. Can’t check it
everytime I change
something”
Somebody: “Build is
too slow, let’s skip
unit tests. Anyway
they don’t add
value”
Doomsday strikes
150. Peppering threading logic in tests
● Makes them harder to read and understand
○ Like production code wasn’t enough
● Makes them flaky/unpredictable
○ Cause yeah getting threading right is so easy!
Right?
Time dependency example
Problems with naive attempt
151. SystemTicker MockTicker
Abstracting System.nanoTime()
<<I>> Ticker
Source for fetching
elapsed time
+ nanoTime()
SystemUnderTest
<<depends>>
long start =
ticker.nanoTime();
User user =
userRepository.getUser(request
.getUserId());
long end = ticker.nanoTime(); return System::nanoTime; // returns what you want
153. LocalDate dependency example
public DiwaliPromotionFactory1(
DiwaliDayProvider diwaliDayProvider) {
this.diwaliDayProvider = diwaliDayProvider;
}
public DiwaliPromotion get() {
LocalDate now = LocalDate.now();
// Calculate which day will be Diwali this year
LocalDate diwali = diwaliDayProvider.get(now.getYear());
return new DiwaliPromotion(diwali.atStartOfDay());
}
How to test?
154. LocalDate dependency example
@Test
public void locatesCurrentYearDiwaliDay() {
DiwaliPromotionFactory1 factory =
new DiwaliPromotionFactory1(mockDiwaliDayProvider);
factory.get();
// Well works all well in 2018, but starts failing in 2019
verify(mockDiwaliDayProvider).get(2018);
}
Bad Attempt - Ticking time bomb
155. LocalDate dependency example
public DiwaliPromotionFactory2(DiwaliDayProvider diwaliDayProvider,
Clock clock) {
this.diwaliDayProvider = diwaliDayProvider;
this.clock = clock;
}
@Test
public void locatesCurrentYearDiwaliDay() {
Clock fixedClock = newFixedClockAt(FIXED_DATE);
DiwaliPromotionFactory2 factory =
new DiwaliPromotionFactory2(mockDiwaliDayProvider, fixedClock);
factory.get();
// Because date is fixed, tests will always pass
verify(mockDiwaliDayProvider).get(FIXED_DATE.getYear());
}
Inject Clock instance
157. Credit Card Processing Example
AuthenticatorPage
R
ChargePage
R
Authenticator
S
CreditCardProcessor
S
UserRepository
S
OfflineQueue
S
Database
S
S - Singleton scope
R - Request scope
Constructor
Collaborator
Same construction and collaboration graph
Credit: Miško Hevery
158. Credit Card Processing Example
Same construction and collaboration graph
● Difficult to test as each object is creating the objects it
needs (it’s ok to create value objects or internal
helpers)
● new operator is scattered all around the codebase
● It’s not the responsibility of business objects to create
objects via new or via getInstance (indirectly new)
159. Credit Card Processing Example
S - Singleton scope
R - Request scope
Constructor
Collaborator
With Dependency Injection
AuthenticatorPage
R
ChargePage
R
Authenticator
S
CreditCardProcessor
S
UserRepository
S
OfflineQueue
S
Database
S
ApplicationFactory
(Singleton lifetime)
S
RequestFactory
(HTTP Request Lifetime)
S
160. Credit Card Processing Example
With Dependency Injection
● The construction (instantiation) of objects is moved to
Factory classes which is where majority of new
operator is located
● Object construction is separated from collaboration
graph
● ApplicationFactory creates all singleton scope
objects
● RequestFactory creates all request scope objects and
depends on singleton scope objects
161. Credit Card Processing Example
With Dependency Injection
// All objects created by application factory are singleton in scope.
public class ApplicationFactory {
private RequestFactory requestFactory;
public void build() {
// leaf of graph has no dependency, so gets created first
Database database = new Database();
OfflineQueue offlineQueue = new OfflineQueue(database);
Authenticator authenticator = new Authenticator(database);
UserRepository userRepository = new UserRepository(database);
CreditCardProcessor creditCardProcessor = new CreditCardProcessor(offlineQueue);
requestFactory = new RequestFactory(authenticator, userRepository,
creditCardProcessor);
}
public RequestFactory getRequestFactory() {
return requestFactory;
}
}
162. Credit Card Processing Example
With Dependency Injection
// All objects created by request factory are HTTP request scope.
public class RequestFactory {
private final Authenticator authenticator;
private final UserRepository userRepository;
private final CreditCardProcessor creditCardProcessor;
public RequestFactory(Authenticator authenticator, UserRepository userRepository,
CreditCardProcessor creditCardProcessor) {
this.authenticator = authenticator;
this.userRepository = userRepository;
this.creditCardProcessor = creditCardProcessor;
}
public AuthenticationPage build() {
return new AuthenticationPage(authenticator,
new ChargePage(creditCardProcessor, userRepository));
}
}
163. Show of hands
How many of you feel with time it gets
difficult to add new features to existing code?
164. Show of hands
How many of you want to change structure of
code to make it easier to add new feature but
can’t do because lack of understanding of
code or lack of confidence?
169. Stubbing
@Test
public void stubbingReturnValueForAnyArgument() {
// For any index value return "fixed value"
// List will behave as if all indexes have value "fixed
value"
Mockito.when(mockedList.get(Mockito.anyInt()))
.thenReturn("fixed value");
assertThat(mockedList.get(999), is(equalTo("fixed value")));
}
Matching any argument - Argument Matchers
174. Don’t Verify Your Own Stubbing
Mockito.when(mockedObject.method(1))
.thenReturn("one");
// This only proves that mocking library works
assertThat(mockedObject.method(1),
is(equalTo("one")));
We don’t want to test mocking library
175. Only verify indirect behavior
Verify how SUT interacted with your mock
WeatherObservable observable = new WeatherObservable();
observable.addObserver(mockObserver1);
observable.setWeather("WINTER");
// Verifying whether mock was indirectly called
verify(mockObserver1).update(observable, "WINTER");
176. Testing Internals
public class Customer {
private CustomerStatus status = CustomerStatus.REGULAR;
public void promote() {
this.status = CustomerStatus.PREFERRED;
}
public double getDisount() {
return this.status == CustomerStatus.PREFERRED ? 0.5 : 0;
}
}
@Test
public void promotingChangesCustomerStatusToPreferred() {
Customer customer = new Customer();
customer.promote();
// Exposing internal state makes tests brittle, what if tomorrow
// we have to refactor and remove enum to use boolean
assertThat(customer.status, is(equalTo(Customer.CustomerStatus.PREFERRED)));
}
Don’t expose internal state just for testing purposes
177. Testing via Public API
@Test
public void aPromotedCustomerEnjoysDiscountedRate() {
Customer customer = new Customer();
customer.promote();
// Checking for side effect using public API and not
checking
// internal state. So Customer can be refactored to use
boolean
// instead of enum easily without breaking unit tests
assertThat(customer.getDisount(), is(equalTo(0.5)));
}
Allows for easier refactoring
178. Hidden Dependencies
public class ClassHidingDependency {
public void doSomething() {
try {
// Looking up objects from thin air
Context.getX().getY().getZmanager().init();
Context.getX().getY().getZmanager().doSomething();
} catch (Exception e) {
// No problem somebody else might have called init.
// Eating exception - Yikes!!
}
}
}
Don’t hide dependencies
179. Explicit Dependencies
public class ClassExplicitDependency {
private final ZManager zManager;
public ClassExplicitDependency(ZManager zManager) {
// zManager instance being passed must be already initialized
this.zManager = zManager;
}
public void doSomething() {
// Directly using ZManager, as it expects an already initialized
object
// to be passed via constructor
zManager.doSomething();
}
}
Perform Dependency Injection
180. Global State Cleanup - reset
public class ResettableSingleton {
public static final ResettableSingleton INSTANCE = new
ResettableSingleton();
private int doneCount;
private ResettableSingleton() {
}
// Will work when single tests are run, but will fail randomly when
tests are run in parallel
public void reset() {
doneCount = 0;
}
}
Doesn’t really work when tests run in parallel
181. Singleton → singleton
// Container can ensure only one instance of Lenient
is created. Hence ensuring singleton property
public class Lenient {
private int doneCount;
public Lenient() {
// Not enforcing singletoness, tests can create independent
instances which are isolated from each other
}
}
Don’t enforce singletoness via private constructor
182. Avoid Threads in Unit Tests
@Test
public void test() throws InterruptedException {
sut.doSomethingInvolvingThreads();
// Don't do this
Thread.sleep(1000);
if (getResult() == null) {
// if result not yet calculated sleep some more
Thread.sleep(1000);
}
Object result = getResult();
// assertion
}
Makes code slower and hard to read and troubleshoot
183. Inject Non-Threaded ExecutorService
private SystemUnderTest sut =
new SystemUnderTest(MoreExecutors.directExecutor());
@Test
public void test() {
sut.doSomethingInvolvingThreads();
// Result is already available because background work was
performed by current thread
Object result = getResult();
}
Use Guava DirectExecutor or FakeExecutor