Get started with Mockito
Get started with Mockito
Software testing is essential to ensure correct working of applications. It also helps speed up the development process as the tests would let us know if changes to the program breaks something. Unit testing is a type of software testing where each smaller part of the application is tested in isolation from other components. These tests are usually run multiple times daily & that too not on the server but on the developer’s computer. Hence, unit tests have to be quick. Apart from increasing the tests time, dependencies also increase chances of a test failing even with correct logic because the test could fail due to a bug in any of the dependencies.
To not care about dependencies, we replace the original implementation of the dependency using test doubles like Mocks, Stubs, Spies. We can stub methods to return predefined values, for example, when calling methods that are not implemented yet. Mocking allows capturing interactions to the mocked objects & returns null for any calls that’s not stubbed. Spying provides similar functionality as of mocking. The difference is that for non-stubbed scenarios, the spied objects work normally.
Mockito is a Java library popularly used in testing to create test doubles.
Understand the need for using Mockito
Understand using Mockito to test predefined behaviour
Understand using Mockito to simulate behaviour based on input
Understand using Mockito to verify interactions with dependencies
Understand common issues and tips working with Mockito
Get started with Mockito
Software testing is essential to ensure correct working of applications. It also helps speed up the development process as the tests would let us know if changes to the program breaks something. Unit testing is a type of software testing where each smaller part of the application is tested in isolation from other components. These tests are usually run multiple times daily & that too not on the server but on the developer’s computer. Hence, unit tests have to be quick. Apart from increasing the tests time, dependencies also increase chances of a test failing even with correct logic because the test could fail due to a bug in any of the dependencies.
To not care about dependencies, we replace the original implementation of the dependency using test doubles like Mocks, Stubs, Spies. We can stub methods to return predefined values, for example, when calling methods that are not implemented yet. Mocking allows capturing interactions to the mocked objects & returns null for any calls that’s not stubbed. Spying provides similar functionality as of mocking. The difference is that for non-stubbed scenarios, the spied objects work normally.
Mockito is a Java library popularly used in testing to create test doubles.
Understand the need for using Mockito
Understand using Mockito to test predefined behaviour
Understand using Mockito to simulate behaviour based on input
Understand using Mockito to verify interactions with dependencies
Understand common issues and tips working with Mockito
~/workspace/bytes/
directory and cd
to it
mkdir -p ~/workspace/bytes/
cd ~/workspace/bytes/
~/workspace/bytes/
directory from here using one of the following commands:
git clone git@gitlab.crio.do:crio_bytes/me_mockito.git
git clone https://gitlab.crio.do/crio_bytes/me_mockito.git
See the attached video to run tests
From the test file, or
From the VS code Test window
See the attached video to debug tests
From the test file, or
From the VS code Test window
Have you come across this cool Google Maps feature where it lets you book Uber/Ola cabs directly from the Maps app itself?
Let’s check the case of Uber cabs. Uber provides REST API to avail their services from other applications. Google Maps could get a list of Uber cabs available from a source to a destination location. The API endpoint for this specific use case is - GET /v1.2/estimates/price
. Similarly, there are API endpoints for other purposes like booking a cab and getting status of a ride.
We’ll be simulating this API call using the getPriceEstimates()
method in src\main\java\external\uber\ExternalUberService.java.
Much like most REST APIs, the response is in JSON format. The JSON response is read as Java objects or objects of the type PriceEstimate
to be more precise. You’ll see that each of the keys in the JSON data has a corresponding field in the PriceEstimate
class to which value of the key gets mapped to.
To summarize the project files:
All classes contain getter and setter methods for their fields
src\main\java\external\uber\ExternalUberService.java - contains methods related to the Uber cab service
getPriceEstimates()
- fetches info on Uber cabs available for a journey using Uber API
buildPriceEstimateBaseUrl()
- builds the Uber API url from start and end location coordinates
src\main\java\external\uber\model\PriceEstimate.java - used to deserialize API response in JSON format to Java object
src\main\java\internal\helper\GoogleMapsHelper.java - contains contract for helper methods in the form of interface
isValidLocations()
- check if the input start and end location coordinates are valid
makeUberPayment
- make payment for a Uber cab
src\test\java\external\uber\ExternalUberServiceTest.java - contains the unit tests
src\test\resources\price_estimate.json - sample Uber API response
Testing code concerned with Uber API in Google Maps without mocking leads to:
Slows the unit tests as it involves network calls to Uber servers
Running the unit tests can become expensive if any of these APIs are rate-limited or charged by Uber
Unit tests cannot be run if the Uber API servers are temporarily down
Let’s check on some scenarios where Mockito helps in more detail
Quite often, different components of a project will be worked on by developers across multiple teams. There will be a contract pre-defined in these scenarios, mostly in the form of interfaces specifying the behaviour of different methods. Your code will be using the components others are working on.
Our getPriceEstimate()
method in the ExternalUberService
class uses the isValidLocations()
method of GoogleMapsHelper
. GoogleMapsHelper
is an interface containing method contracts being worked on by a different team yet to be implemented. Should you wait for the other team to complete their code to start unit testing your implementation?
public interface GoogleMapsHelper {
/**
* Validates the correctness of the starting and ending location coordinates
*
* @param startLatitude of type Double.
* @param startLongitude of type Double.
* @param endLatitude of type Double.
* @param endLongitude of type Double.
* @return true if coordinates given are all valid,
* false if any of the coordinate values are invalid
* Valid Latitude range -> -90 to +90
* Valid Longitude range -> -180 to +180
*/
public boolean isValidLocations(Double startLatitude, Double startLongitude,
Double endLatitude, Double endLongitude);
/**
* Makes payment for the Uber cab booked via Google Maps
*
* @param tripID of type String
* @param otp of type String
* @return true if the payment was success,
* false if the payment failed
*/
public boolean makeUberPayment(String tripID, String otp);
}
Unit testing by definition should only be testing for a specific unit or functionality. Let’s say we are unit testing the getPriceEstimates()
method. Now, what happens if the buildPriceEstimateBaseUrl()
method has a bug?
Yeah, the unit test for getPriceEstimates()
will also fail. This is not a preferable behaviour. In large projects with multiple dependencies, it’ll be hard to pinpoint the cause of unit test failure if the unit tests aren’t isolated from the dependencies. Also, as buildPriceEstimateBaseUrl()
will be unit tested separately, it’s safe to assume that it works as expected when testing getPriceEstimates()
.
How would you isolate the dependencies when unit testing?
It’s critical that our code be tested to ensure its correctness as well as to check how it handles irregularities. What would happen if Uber API response structure got changed in a future release or a bug introduced returns an empty response occasionally?
To check how our code behaves in these cases, we’ll have to get these exact responses back from the Uber API server. But, how would you do that? These are scenarios we are anticipating and don't happen today.
Mockito can be used to return preset responses to deal with all the 3 scenarios we discussed without making any changes to the implementation.
ExternalUberServiceTest
class has a getPriceEstimateBaseUrlTest()
method. This is a JUnit only unit test for the buildPriceEstimateBaseUrl()
method and doesn’t involve Mockito. Run it and you’ll see it pass.
We’ll be writing unit tests for the getPriceEstimates()
method. The getPriceEstimates()
method should return an empty array if any of the input coordinates are invalid. The getPriceEstimatesWithoutMockTest()
test checks for this case.
public void getPriceEstimatesWithoutMockTest() {
// Setup
Double startLatitude = 137.775231;
Double startLongitude = -122.418075;
Double endLatitude = 37.775241;
Double endLongitude = -122.518075;
ExternalUberService externalUberService = new ExternalUberService();
// Get output of the method under test
PriceEstimate[] actualPriceEstimates = externalUberService.getPriceEstimates(
startLatitude, startLongitude, endLatitude, endLongitude);
// Check if the returned value matches expected value
int expectedLength = 0;
assertEquals(expectedLength, actualPriceEstimates.length);
}
TODO -
Run getPriceEstimatesWithoutMockTest()
. Does the test pass?
Check the error response. Why do you think the test is failing?
You’d have found that the test failed.
The test report is mentioning that the googleMapsHelper
field is null
. You’d recall isValidLocations()
is an abstract method in the GoogleMapsHelper
interface. (Comment out getPriceEstimatesWithoutMockTest()
, we’ll write a different test that works)
To test getPriceEstimates()
, we’ll mock GoogleMapsHelper
and set the googleMapsHelper
field of the ExternalUberService
instance as this mock object in the test. This allows you to pre-set an output which is returned when isValidLocations()
is called with a specific input from the test. By mocking, we’re basically removing the dependency of the Code Under Test (getPriceEstimates()
) on isValidLocations()
The getPriceEstimatesReturnsEmptyArrayOnInvalidStartLatitudeTest()
test checks if the getPriceEstimates()
method returns an empty array when the starting location latitude is invalid. You can see the valid range of latitude is from -90 to +90 in the GoogleMapsHelper
contract. The starting location latitude given in the test doesn’t fall in this interval.
Below is the test code
// 1
@Mock
private GoogleMapsHelper googleMapsHelperMock;
public void getPriceEstimatesReturnsEmptyArrayOnInvalidStartLatitudeTest() {
// 2. Setup
Double startLatitude = 137.775231;
Double startLongitude = -122.418075;
Double endLatitude = 37.775241;
Double endLongitude = -122.518075;
// 3.
ExternalUberService externalUberService = new ExternalUberService();
externalUberService.setGoogleMapsHelper(googleMapsHelperMock);
// 4. Setup mock to return preset value of false
// when startLatitude parameter is 137.775231
when(googleMapsHelperMock.isValidLocations(eq(137.775231), anyDouble(), anyDouble(), anyDouble()))
.thenReturn(false);
// 5. Get output of the method under test
PriceEstimate[] actualPriceEstimates = externalUberService.getPriceEstimates(
startLatitude, startLongitude, endLatitude, endLongitude);
// 6. Check if the returned value matches expected value
int expectedLength = 0;
assertEquals(expectedLength, actualPriceEstimates.length);
}
The @Mock
annotation is used to create a Mock object of type GoogleMapsHelper
, googleMapsHelperMock
(this is done at the class level)
The input coordinate values are initialized
Created an ExternalUberService
object and set its googleMapsHelper
field value to googleMapsHelperMock
Use Mockito when().thenReturn()
statement to return false
when the isValidLocations()
method of the googleMapsHelperMock
object is called with the the startLatitude
value of 137.775231 and any Double value for other parameters. eq()
and anyDouble()
are Mockito argument matchers. anyDouble()
matches with any Double value while eq()
requires the exact value inside it to get matched.
The function to test is called
Validate if the output is as expected
We’ve created the Mock object for googleMapsHelper
and set it to return false
when called with the correct arguments. The test should pass now, right? Try running it.
It’s still failing!
To allow the JUnit test class to support Mocks, we need to register the Mockito extension for the test class. @ExtendWith(MockitoExtension.class)
annotation does this.
@ExtendWith(MockitoExtension.class)
public class ExternalUberServiceTest {
The test will pass now.
Confused which object(s) to Mock and what to return? Use this framework to your aid
Code Under Test: ExternalUberService.getPriceEstimates()
Test case: Check if ExternalUberService.getPriceEstimates() returns empty
array on invalid latitude for start location
Dependency Layer/Function:
1. googleMapsHelper.isValidLocation(String startLatitude, String startLongitude,
String endLatitude, String endLongitude);
Mock Setup:
- Function to mock: googleMapsHelper.isValidLocation()
- Input to mock: (some invalid start latitude, any start longitude, any end
latitude, any end longitude)
- Pre-set output from mock: false
Test Input: externalUberService.getPriceEstimates()(parameters should be
inline with your mock setup)
Expected Test Output: Empty price estimate array
Feel free to add more tests to check if getPriceEstimates()
return an empty array when the other coordinates (startLongitude, endLatitude, endLongitude) are invalid
We used eq(137.775231)
inside the when().thenReturn()
statement for getPriceEstimatesReturnsEmptyArrayOnInvalidStartLatitudeTest()
. Wouldn’t just 137.775231
suffice? Can you verify that?
Similar to when().doReturn()
, Mockito provides another statement doReturn().when()
. Can you check its usage & re-write the stubbings using doReturn().when()
?
Let’s now test getPriceEstimates()
to return the API response as PriceEstimate
objects on a valid input coordinates and successful API call. The getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
does this. A mock object was used earlier for googleMapsHelper
to avoid the test’s dependency on it. Similarly to make the test independent of the API response, the restTemplate
object (which fetches the API response) will also be mocked.
ExternalUberService externalUberService = new ExternalUberService();
externalUberService.setGoogleMapsHelper(googleMapsHelperMock);
externalUberService.setRestTemplate(restTemplateMock);
TODO - Complete the below framework for this test case and answer the first question at the end of this milestone
Code Under Test:
Test case:
Dependency Layer/Function:
1.
2.
Mock Setup:
- Function to mock:
- Input to mock:
- Pre-set output from mock:
- Function to mock:
- Input to mock:
- Pre-set output from mock:
Test Input:
Expected Test Output:
The isValidLocations()
call is set to return true
The getForObject()
call returns data read from the price_estimate.json file using loadPriceEstimateList()
method in the test
Earlier, getPriceEstimatesReturnsEmptyArrayOnInvalidStartLatitudeTest()
was used to test the getPriceEstimates()
method. If you check the test, we were using the when().thenReturn()
statement to hardcode the return value when the isValidLocations()
method was called. Let’s see how to use the actual parameter values to isValidLocations()
for deciding what to return.
The current test uses Mockito Answer
interface and doAnswer().when()
method for this.
Answer<Boolean> validity = new Answer<Boolean>() {
public Boolean answer(InvocationOnMock invocation)
throws Throwable {
// Get the first argument to the invoked method
Double startLatitude = invocation.getArgument(0);
// Valid latitude is in -90 to +90
if (Math.abs(startLatitude) <= 90 ) {
return true;
} else {
return false;
}
}
};
// Return the value of `validity` when the method of the Mock is invoked
doAnswer(validity).when(googleMapsHelperMock).isValidLocations(eq(37.775231), anyDouble(), anyDouble(), anyDouble());
Some points to understand Answer
:
Answer
is a generic interface which is why we’re passing the return type Boolean
all over the definitions
We have to implement the answer()
method which takes in a single fixed argument of type InvocationOnMock
. The InvocationOnMock
argument acts as a placeholder for the called mock method and thus we can use it to fetch the parameters with which the method was called. This is done using getArgument()
In addition to checking the length of the array returned, the test checks for the output array content as well.
assertEquals(3, actualPriceEstimates.length);
assertEquals("₹110-140", actualPriceEstimates[0].getEstimate());
assertEquals("₹110-130", actualPriceEstimates[1].getEstimate());
assertEquals("₹210", actualPriceEstimates[2].getEstimate());
Run the getPriceEstimatesReturnsEmptyArrayOnInvalidStartLatitudeTest()
test, it should pass.
null
or does it depend on the method’s return type?Each of the tests were initialising an ExternalUberService
object till now. Also, we’ve been using the setter method of the ExternalUberService
class to assign its googleMapsHelper
field as the Mock object.
ExternalUberService externalUberService = new ExternalUberService();
externalUberService.setGoogleMapsHelper(googleMapsHelperMock);
externalUberService.setRestTemplate(restTemplateMock);
TODO -
Comment out the above lines in getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
and run the test
Read and understand the error
The test fails as we haven’t initialized externalUberService
.
We can resolve this error for now by initialising the externalUberService
at the class-level.
Set the below line at the start of ExternalUberServiceTest
private ExternalUberService externalUberService;
to
private ExternalUberService externalUberService = new ExternalUberService();
TODO - Run the test again
Now, the test will fail again as expected due to invoking the googleMapsHelper
field which is null
as of now.
Mockito @InjectMocks
annotation can be used to automatically initialize the class we’re testing as well as to inject the Mock objects it requires. Add the @InjectMocks
annotation to externalUberService
field and remove the initialization.
@InjectMocks
private ExternalUberService externalUberService;
Run getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
again, it should pass now. You can remove similar lines from other tests as well and see that they run successfully as well.
Now, how did Mockito "inject" the mocks to externalUberService
?
Set breakpoints inside setRestTemplate()
and setGoogleMapsHelper()
methods in ExternalUberService
.
Start the getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
test in debug mode. What do you find?
The debugger will stop inside the setGoogleMapsHelper()
method. Similarly, it will reach inside the setRestTemplate()
method as well. This is how @InjectMocks
is setting the Mock objects without us explicitly calling the setter method in each individual test
getPriceEstimateBaseUrlTest()
doesn’t require any of the mocks. Do the mocks get injected when this test only is run?Till now, we’ve been testing the getPriceEstimate()
method for only the output it returned. How would we validate the correct methods are called with correct arguments?
TODO - Uncomment the getPriceEstimatesWithBug()
method in ExternalUberService
class.
You’ll be able to find that the arguments to isValidLocations()
are ordered incorrectly. Both the latitude values are coming first instead of latitude and longitude of the starting location first.
if (!googleMapsHelper.isValidLocations(startLatitude, endLatitude, startLongitude, endLongitude)) {
TODO - Uncomment the getPriceEstimatesWithBugTest()
method.
This is the exact test as getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
. The difference being the code under test now is getPriceEstimatesWithBug()
instead of getPriceEstimates()
. Run the test and see if it fails.
No, right?
Mockito provides a verify()
statement to check if a Mock is being interacted with during the test with the correct set of arguments.Add the below line of code to the end of the test.
verify(googleMapsHelperMock).isValidLocations(startLatitude, startLongitude, endLatitude, endLongitude);
TODO -
Run the test again and see it failing.
Read through the error and understand what happened
Check the error trace to find the line of code in the test which threw it
The test fails on the verify()
call and error message says that the Mock was expecting an invocation with arguments, 37.775231, -122.418075, 37.775241, -122.518075 but received 37.775231, 37.775241, -122.418075, -122.518075.
Correct the ordering of isValidLocations()
arguments and ensure the test passes.
Mockito ArgumentCaptor
is used to capture the arguments by which method of a mocked object is called.
ArgumentCaptor<String> quantityCaptor = ArgumentCaptor.forClass(String.class);
verify(restTemplateMock).getForObject(quantityCaptor.capture(), eq(PriceEstimate[].class));
First, initialize a ArgumentCaptor
object of the data type same as the argument we need to capture
Use the capture()
method when calling verify()
to capture the argument
The captured value now can be used to check if it matches the expected one
String actualUrl = quantityCaptor.getValue();
assertEquals(url, actualUrl);
getPriceEstimates()
call. Does the test fail now if the getPriceEstimates()
implementation contains multiple restTemplate.getForObject()
calls? How would you test for this scenario?Let’s see how to check if the Mock used in a test is actually getting called.
TODO -
Put a breakpoint in the GoogleMapsHelper
interface on the isValidLocations()
method.
Start the getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
test in debug mode. Does the control go to GoogleMapsHelper
?
You’ll find that the debugger doesn’t stop at the breakpoint in GoogleMapsHelper
. The test will run to completion (if you don’t have breakpoints set anywhere else)
TODO -
Put a breakpoint on the isValidLocations()
method call in the getPriceEstimates()
Start the getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
test in debug mode.
Hover over the googleMapsHelper
field or use the Debug console to print it out. Is it of type GoogleMapsHelper
?
You’ll find that the googleMapsHelper
instance is Mock version of GoogleMapsHelper
FInd out where in ExternalUberService
class we are initialising the restTemplate
and googleMapsHelper
fields.
No where in the class will you be able to find out these objects instantiated using the new
keyword. We’re using the Spring framework’s @Autowired
annotation on both of these fields. This way we’re outsourcing to Spring, the responsibility of instantiating these whenever the program is run. This also works in harmony with Mockito. Let’s see what happens to the tests if we initialize these using the new
keyword.
TODO -
restTemplate.getForObject()
call in getPriceEstimates()
restTemplate = new RestTemplate();
Put a breakpoint
Inside the setRestTemplate()
method
On the line where restTemplate.getForObject()
is called inside getPriceEstimates()
Start the getPriceEstimatesValidLocationReturnsPriceEstimatesTest()
test in debug mode.
Is restTemplate
a Mock object or real RestTemplate
object when
When the debugger stops inside setRestTemplate
When the debugger stops inside the getPriceEstimates()
method
You’d have found that restTemplate
was set to a Mock object initially in the setter but got overridden as it was re-initialized inside the getPriceEstimates()
method. If you ran the test to completion, the error will tell you about an authorization exception. This happened because the Uber API got called actually which requires an authentication token to be sent as well.
Mockito can fail due to subtle differences in the Mock setup and it can become hard to debug the error by just glancing through the code.
TODO -
Uncomment the mockitoSetupErrorTest()
test and run it
Run the test and try to understand the error.
To debug your way out of it, comment out the current when().thenReturn()
statement and start with more general arguments
Use when(googleMapsHelperMock.isValidLocations(any(), any(), any(), any())) .thenReturn(false);
. Does the test pass now? Passing test means the Mock setup was called
Use Use
when(googleMapsHelperMock.isValidLocations(anyDouble(), anyDouble(), anyDouble(), anyDouble())).thenReturn(false)`. Does the test pass now?
Similarly, try to go on being more and more specific to find out the issue
Mockito throws the UnnecessaryStubbingException
if the setup using when().thenReturn()
doesn’t get invoked during the test. This can be a desirable behaviour from the point of view of the developer because if we used a when().thenReturn()
in the test, it’s either
we wanted it to get invoked. Not getting invoked would mean there’s some error, or
the stubbing isn’t required in the first place
TODO -
Uncomment the unnecessaryStubbingExceptionTest()
test and run it
You’ll get the error report with the UnnecessaryStubbingException
. Read through it to understand the issue
What and why do you think the error was thrown?
The test is for the buildPriceEstimateBaseUrl()
. The test has stubbed the isValidLocations()
method but the method under test doesn’t use the isValidLocations()
method. Removing the when().thenReturn()
method solves the problem here.
Test doubles are used to replace original implementation for the purpose of testing. They include mocks, stubs and spies.
Using Mockito to deal with dependencies in unit tests can
Reduce time taken to run unit tests by removing expensive I/O operations like network requests, database operations or file I/O
Avoid cost for running unit test by not making API calls or requests that are paid
Isolate unit tests from dependencies
Allow to test for conditions that doesn’t exist or cannot be created otherwise.
Mockito dictionary
@ExtendWith(MockitoExtension.class)
registers the test class to support Mockito
@Mock
creates a mock of an object
@InjectMocks
injects the dependent mock objects to the object annotated with it
when().thenReturn()
stubs methods of mocked objects with values to return when called with appropriate arguments
doAnswer()
stubs method based on the argument value to the mock method
verify()
validates that the mock was interacted with during the course of the test
ArgumentCaptor
can capture arguments by which the mock methods are called during the test
Though Mockito is tricky to learn and get right, the errors usually contain good debugging information. Try to understand what the error is saying to debug more efficiently.
Find the solutions to the Curious Cats questions here
Further Reading
Write more efficient and complex unit tests using Mockito
Write unit tests that are independent of dependencies
Write unit tests to test for more vivid behaviours