Skip to content

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 arguments
  • thenReturn() - Return a predefined value to the caller that has been created elsewhere
  • thenThrow() - 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
pom.xml
// ...
<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.

MockitoExtension.java
// 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:

Without @Mock
@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:

With @Mock
@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:

Without @Spy
@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:

With @Spy
@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:

Without @Captor
@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:

With @Captor
@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:

@InjectMock example
@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"));
}
Dictionary.class
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.

Sensor.class
package io.entityfour.tdd.aircon;

public class Sensor {

 private boolean blocked;

 public boolean isBlocked() {
  return blocked;
 }

 public void setBlocker(boolean blocked) {
  this.blocked = blocked;
 }
}
Thermometer.class
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;
 }

}
ThermometerTest.class
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

Credentials.java
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);
    }
}
Database.java
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;
    }
}
DatabaseAccessTest.java
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

Database.class
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();
    }
}
DatabaseUtils.java
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;
    }
}
StatisticsTest.java
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));

    }
}