Mockito
Estimated time to read: 11 minutes
SUT and DOC¶
System Under Test (SUT) and Depended-On-Components (DOC). These are both tightly coupled together in an application.
Using mock objects is a way of reducing the strength of the coupling, as a mock object can act as a DOC where needed and simulate it.
This enables developers to focus on the system / class that is being tested (SUT) rather than having to worry about external dependant components such as databases, repositories, networking, etc as long as long as it returns the expected result for the system.
Paths¶
SAD Path¶
If the DOC returns an error, this path covers the implementation of the error handling within the system.
HAPPY Path¶
If the DOC returns data or the expected output, this path covers the implementation of the logic within the system
Mock Objects¶
Mock objects can simulate the behaviour of complex objects. This makes them extremely useful when testing on a dependant on component is impractical or impossible to incorporate into a unit test.
- Mock objects are simulated objects that mimic the behavior of real objects in a controlled way.
- A mock object is typically created to test the behaviour of another object.
The mock object removes the need to have any additional logic or methods within a class for testing purposes.
Use cases¶
Mock objects have many use cases and variations, below are a few examples, note that this list is not exhaustive:
- Supply non-deterministic results - For example, the current time
- States that are difficult to reproduce - For example, a network error
- Bandwidth / Speed - For example, a database initialisation procedure
- Consistency - For example, if the implementation changes the mock object is still has the same behaviour
By default, mock objects have no behaviour. Once a mock object has been declared via Mockito and the @Mock
annotation, the following methods can be used as needed:
when()
- When the object in question is called, with the given argumentsthenReturn()
- Return a predefined value to the caller that has been created elsewherethenThrow()
- Throws an exception
Mockito¶
Mockito is a library that can be used to instantiate mock objects within the codebase.
Setup¶
To use Mockito, it must be added as a dependency to the projects pom.xml
using the following dependency structure.
- groupId: org.mockito
- ArtifactID: mockito-core
- Version: 4.x.x
// ...
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.x.x</version>
<scope>test</scope>
</dependency>
<!-- Also include the Mockito JUnit dependency.-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>2.23.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- And the Surefire plugin -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</plugin>
// ...
Adding org.mockito:mockito.junit-jupiter
as a dependency automatically brings the MockitoExtension.java
file into the project, see below for reference.
// The class, MockitoExtension, implements the TestInstancePostProcessor and ParameterResolver interfaces from JUnit 5
public class MockitoExtension implements TestInstancePostProcessor, ParameterResolver {
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
MockitoAnnotations.initMocks(testInstance); // Initialise Mocks
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().isAnnotationPresent(Mock.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return getMock(parameterContext.getParameter(), extensionContext);
}
private Object getMock(Parameter parameter, ExtensionContext extensionContext) {
Class<?> mockType = parameter.getType();
Store mocks = extensionContext.getStore(Namespace.create(MockitoExtension.class, mockType));
String mockName = getMockName(parameter);
if (mockName != null) {
return mocks.getOrComputeIfAbsent(mockName, key -> mock(mockType, mockName));
}
else {
return mocks.getOrComputeIfAbsent(mockType.getCanonicalName(), key -> mock(mockType));
}
}
private String getMockName(Parameter parameter) {
String explicitMockName = parameter.getAnnotation(Mock.class).name().trim();
if (!explicitMockName.isEmpty()) {
return explicitMockName;
}
else if (parameter.isNamePresent()) {
return parameter.getName();
}
return null;
}
}
Annotations¶
Code examples courtesy of Baeldung
@Mock
¶
The @Mock
annotation is used to create and inject mocked instances. Real objects are not created, rather Mockito creates a mock object for the class. Essentially, Mocktio looks at the class to be mocked and will then create a mocked object using the class' behaviour.
In the following example, a mocked ArrayList is created manually without using the @Mock annotation:
@Test
public void whenNotUseMockAnnotation_thenCorrect() {
List mockList = Mockito.mock(ArrayList.class);
mockList.add("one");
Mockito.verify(mockList).add("one");
assertEquals(0, mockList.size());
Mockito.when(mockList.size()).thenReturn(100);
assertEquals(100, mockList.size());
}
Now we'll do the same, but we'll inject the mock using the @Mock annotation:
@Mock
List<String> mockedList;
@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
@Spy
¶
The @Spy
annotation is used to create a real object and spy on that real object. A spy helps to call all the normal methods of the object while still tracking every interaction, just as we would with a mock.
In the following example, a spy of a List is created without using the @Spy annotation:
@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
List<String> spyList = Mockito.spy(new ArrayList<String>());
spyList.add("one");
spyList.add("two");
Mockito.verify(spyList).add("one");
Mockito.verify(spyList).add("two");
assertEquals(2, spyList.size());
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
}
Now the same exercise, spy on the list, but using the @Spy annotation:
@Spy
List<String> spiedList = new ArrayList<String>();
@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
spiedList.add("one");
spiedList.add("two");
Mockito.verify(spiedList).add("one");
Mockito.verify(spiedList).add("two");
assertEquals(2, spiedList.size());
Mockito.doReturn(100).when(spiedList).size();
assertEquals(100, spiedList.size());
}
@Captor
¶
The @Captor
annotation is used to create an ArgumentCaptor instance which is used to capture method argument values for further assertions.
In the following example, an ArgumentCaptor is created without using the @Captor annotation:
@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
List mockList = Mockito.mock(List.class);
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("one");
Mockito.verify(mockList).add(arg.capture());
assertEquals("one", arg.getValue());
}
Now the same exercise, using @Captor
to create an ArgumentCaptor instance:
@Mock
List mockedList;
@Captor
ArgumentCaptor argCaptor;
@Test
public void whenUseCaptorAnnotation_thenTheSam() {
mockedList.add("one");
Mockito.verify(mockedList).add(argCaptor.capture());
assertEquals("one", argCaptor.getValue());
}
@IntjectMocks
¶
@InjectMocks
marks a field on which injection should be performed. Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection – in this order. If any of the given injection strategies fail, then Mockito won’t report a failure.
In the following example, @InjectMocks
is used to inject the mock wordMap into the MyDictionary dic:
@Mock
Map<String, String> wordMap;
@InjectMocks
MyDictionary dic = new MyDictionary();
@Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
assertEquals("aMeaning", dic.getMeaning("aWord"));
}
public class MyDictionary {
Map<String, String> wordMap;
public MyDictionary() {
wordMap = new HashMap<String, String>();
}
public void add(final String word, final String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(final String word) {
return wordMap.get(word);
}
}
Non-Declarative / External Device Example¶
Activity Diagram¶
Below is an activity diagram for implementing an air conditioning system within the application.
%%{init: {'flowchart' : {'curve' : 'linear'}}}%%
flowchart
AA( )
ZZ( )
A[Check the sensor]
B{ }
C[throw RuntimeException]
D{ }
E[Open air conditioning]
F{ }
AA --> A
A --> |Sensor is blocked?| B
B --> |Yes| C
C --> F
F --> ZZ
B --> |temperature >= threshold| D
D --> |Yes| E
D --> F
E --> F
In the activity diagram above, the system is reliant on some devices that provide non-deterministic results. For example, the temperature object. These objects can be implemented with a mock object, which means that the flow of the activity diagram can be tested having to use a DOC.
Component Relationship¶
Using the activity diagram above as an example, the relationship between the objects is as follows:
graph LR
A[Air Conditioning System] --> B[Thermometer] --> C[Sensor]
The Air Conditioning System is dependant on the information received from the Thermometer which in turn is dependant on the information received from the Sensor
Code¶
Sensor.class
and Thermometer.class
below are the implementations of their corresponding objects.
package io.entityfour.tdd.aircon;
public class Sensor {
private boolean blocked;
public boolean isBlocked() {
return blocked;
}
public void setBlocker(boolean blocked) {
this.blocked = blocked;
}
}
package io.entityfour.tdd.aircon;
public class Thermometer {
private double temperature;
private Sensor sensor; // Thermometer has a reference to Sensor
public double getTemperature () { // getTemperature should only work when the sensor is NOT blocked. Otherwise, throw a RuntimeException
if (sensor.isBlocked()) {
throw new RuntimeException("Sensor is blocked")
}
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
public Sensor getSensor() {
return sensor;
}
public void setSensor(Sensor sensor) {
this.sensor = sensor;
}
}
package io.entityfour.tdd.aircon;
// ...
@ExtendWith(MockitoExtension.class) // @ExtendWith annotates the class to be extended with the MockitoExtension.class
public class ThermometerTest {
@InjectMocks // Inject any @Mock annotated objects into the defined annotated object
Thermometer thermometer;
@Mock // Create a mock object that essentially copies the behaviour of the annotated object
Sensor sensor;
@Test
void testWorkingSensor() {
thermometer.setTemperature(25.0); // Sets the temperature
when(sensor.isBlocked()).thenReturn(false); // Sets the behaviour of the sensor to be unlocked
assertEquals(sensor, thermometer.getSensor()); // Check to see if the sensor has been injected into the thermometer
assertEquals(25.0, thermometer.getTemperature(), 0.001); // Verifies the value
verify(sensor, times(1)).isBlocked(); // Verifies that the sensor has been blocked once
}
@Test
void testBlockedSensor() {
thermometer.setTemperature(25.0); // Sets the temperature
when(sensor.isBlocked()).thenReturn(true); // Sets the behaviour of the sensor to be blocked
assertEquals(sensor, thermometer.getSensor());// Check to see if the sensor has been injected into the thermometer
assertThrows(RuntimeException.class, () -> thermometer.getTemperature()); //Verifies that the RuntimeException has been thrown
verify(sensor, times(1)).isBlocked(); // Verifies that the sensor has been blocked once
}
}
Database Example¶
Activity Diagram¶
Below is an activity diagram for implementing a database system within the application.
graph TB
AA(Input)
ZZ(Output)
A[Check Credentials]
B{ }
C[Deny Login]
D[Allow Login]
E{ }
AA --> A
A --> |User and Password Match?| B
B --> |No| C
B --> |Yes| D
C --> E
D --> E
E --> ZZ
Component Relationship¶
Using the activity diagram above as an example, the relationship between the objects is as follows:
graph LR
A[Credentials] --> B[Database]
The Credentials object is dependent on the data received from the Database object. In this instance, the Database object is a DOC. A mock object can be used to provide a mock implementation without having to act on a real object.
Code¶
package io.entityfour.tdd.database;
// ...
public class Credentials {
private String username; // Username field
private String password; // Password field
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// The @Override below ensures that two different objects, with the same value, will act the same
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Credentials that = (Credentials) o;
return Objects.equals(username, that.username) && //Return Equal if username equals that.username
Objects.equals(password, that.password); // Return Equal if password equals that.password
}
@Override
public int hashCode() {
return Objects.hash(username, password);
}
}
package io.entityfour.tdd.database;
// ...
public class Database {
private Map<String, String> registeredUsers = new HashMap<>(); // Provides a map of `registeredUsers`
List<List<String>> queriedData = new ArrayList<>();
public boolean login(Credentials credentials) { // Provides a login method, which checks the existence of provided credentials
String username = credentials.getUsername();
String password = credentials.getPassword();
return registeredUsers.keySet().contains(username) &&
registeredUsers.get(username).equals(password);
}
public List<List<String>> queryFlightsDatabase() {
return queriedData;
}
}
package io.entityfour.tdd.database;
// ...
@ExtendWith(MockitoExtension.class) // @ExtendWith annotates the class to be extended with the MockitoExtension.class
public class DatabaseAccessTest {
@Mock // @Mock has been used to create a mock database object, using the behaviours from Database.java
private Database database;
private Credentials credentials = new Credentials("user", "password");
@Test
void testUserSuccessfulLogin() {
when(database.login(credentials)).thenReturn(true);
Credentials sameCredentials = new Credentials("user", "password");
assertTrue(database.login(sameCredentials));
}
@Test
void testUserFailedLogin() {
when(database.login(credentials)).thenReturn(true);
Credentials otherCredentials = new Credentials("user", "password");
otherCredentials.setUsername("otherUser");
otherCredentials.setPassword("otherPassword");
assertNotEquals(credentials.getUsername(), otherCredentials.getUsername());
assertNotEquals(credentials.getPassword(), otherCredentials.getPassword());
assertNotEquals(credentials.hashCode(), otherCredentials.hashCode());
assertFalse(database.login(otherCredentials));
}
}
Statistics Example¶
Overview¶
The new functionality requires access to the Database so that the information is retrieved, calculations are made and statistics are provided. However, the real Database object is not accessible; in addition if working from a real Database object, the data received may change between each execution of the test suite. This means that it would be impossible to create a consistent check. Mock objects and implementations can be used in this instance.
Code¶
package io.entityfour.tdd.database;
// ...
public class Database {
private Map<String, String> registeredUsers = new HashMap<>();
List<List<String>> queriedData = new ArrayList<>(); // Statistic data will be gathered here, inside the queriedData List of Lists of Strings.
public boolean login(Credentials credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
return registeredUsers.keySet().contains(username) &&
registeredUsers.get(username).equals(password);
}
public List<List<String>> queryFlightsDatabase() {
return queriedData;
}
// The three methods below require statistic aggregation abilities
// Data will be gathered into the `queriedData` list above
public double averageDistance(List<Flight> flightsList) {
return flightsList.stream()
.mapToDouble(Flight::getDistance)
.average()
.getAsDouble();
}
public int minimumDistance(List<Flight> flightsList) {
return flightsList.stream()
.mapToInt(Flight::getDistance)
.min()
.getAsInt();
}
public int maximumDistance(List<Flight> flightsList) {
return flightsList.stream()
.mapToInt(Flight::getDistance)
.max()
.getAsInt();
}
}
package io.entityfour.tdd.database;
// ...
// Data received from the database are required to be transformed into a list of flights
// The DatabaseUtil class has had this task delegated to it
public class DatabaseUtil {
private DatabaseUtil() { // Setting a private constructor can help show that this is a utility class...
}
public static List<Flight> buildFlightsList(List<List<String>> queriedData) { // ... along with a static method
List<Flight> flightsList = new ArrayList<>();
for (List<String> row: queriedData) {
Flight flight;
if (row.get(1).equals("e")) {
flight = new EconomyFlight(row.get(0));
} else if (row.get(1).equals("b")) {
flight = new BusinessFlight(row.get(0));
} else {
flight = new PremiumFlight(row.get(0));
}
flight.addPassenger(new Passenger(row.get(2), Boolean.valueOf(row.get(3))));
flight.setDistance(Integer.valueOf(row.get(4)));
flightsList.add(flight);
}
return flightsList;
}
}
package io.entityfour.tdd.database;
// ...
@ExtendWith(MockitoExtension.class) // @ExtendWith annotates the class to be extended with the MockitoExtension.class
public class StatisticsTest {
// @Mock // Using the @Mock annotation her is incorrect. This forces Mockito to provide a substitute for all of the fucntionalities within the Database class / object.
@Spy // The @Spy annotation can be used to keep the functionality of other methods.
private Database database;
private List<List<String>> queriedData;
private List<Flight> flightsList;
@BeforeEach
void before() {
queriedData = new ArrayList<>();
List<String> row1 = Arrays.asList("1", "e", "Mike", "false", "349");
List<String> row2 = Arrays.asList("2", "b", "John", "true", "278");
List<String> row3 = Arrays.asList("3", "e", "Mike", "false", "319");
List<String> row4 = Arrays.asList("4", "p", "John", "true", "817");
List<String> row5 = Arrays.asList("5", "e", "Mike", "false", "623");
List<String> row6 = Arrays.asList("6", "e", "John", "true", "978");
queriedData.add(row1);
queriedData.add(row2);
queriedData.add(row3);
queriedData.add(row4);
queriedData.add(row5);
queriedData.add(row6);
}
@Test
void testQueriedData() {
when(database.queryFlightsDatabase()).thenReturn(queriedData); // Sets the data to be queried
flightsList = buildFlightsList(queriedData);
assertEquals(6, database.queryFlightsDatabase().size()); // Assert expected results...
assertEquals(6, flightsList.size());
assertEquals(560.666, database.averageDistance(flightsList), 0.001);
assertEquals(278, database.minimumDistance(flightsList));
assertEquals(978, database.maximumDistance(flightsList));
}
}