Mockito
Overview
Effective testing is a skill expected from developers. The interviewer often asks questions like,
- How do you test your feature?
- What is your code coverage?
- How do you mock external dependency?
Developers are expected to write Unit test cases to verify their code and existing features. There is no clear definition of the size of the test cases, but it should verify one business functionality. Unit test cases are not meant to test application end to end.
The most popular frameworks for writing unit test cases are Junit and Mockito.
Prerequisite
Readers must have a working knowledge of java and Junit to best use the article.
Introduction to Mockito
For simple applications, writing unit testing is easier but with distributed system and microservices architecture writing unit test case tend to get complex. A unit test case should not test the application end to end; therefore, the dependent system should be mocked to provide the desired behavior. For example, for a typical e-commerce application, unit testing should be contained within the service boundary, and the rest all the dependencies should be mocked.
Unit test cases for Account service should mock Account DB, Inventory Service, and Shipping service.
Mockito is a mocking framework for java based applications that are used for effective unit testing. Mockito is used to mock external interfaces so that a dummy functionality can be added to a mock interface used in unit testing.
Setting up Mockito is a matter of downloading some JARs and using the classes. Luckily spring-boot-starter-test dependency brings in both Junit and Mockito JAR.
Account microservice has one feature for registering a new user. We will build a user "Register User" functionality and write a corresponding unit test case for it.
- Register user functionality depends on the interactive map to pinpoint the location and retrieve the address details. Third-party API providers like mapquest.com provide the interactive map.
Mockito’s Mock Methods
We will write a unit test case for our registration service. But the registration service depends on AddressRetriever, which depends on the REST API provided by mapquest.com. To test our feature, we need to mock the AddressRetriever.
Two steps are involved in setting up mock behavior.
- Setup mocks - We must mock the dependency using the mockito's Mock() method or annotations.
- Set expectation - Mockito provides when(...).then*(...) pattern to set the expectation from external dependency.
Let's code our service and test the case RegistrationServiceTest.
RegistrationService
Registration Service Test
Code Explanation
And here’s a paraphrase of the preceding code:
Setup Mocks
- On-Line number 03-04 Declare the addressRetriever field and annotate it with @Mock, indicating that it’s where we want the mock to be synthesized.
- On-Line number 06-07 Declare the registrationService field and annotate it with @InjectMocks, indicating that it’s where we want the mock to be injected.
- On-Line number 12 Call MockitoAnnotations.initMocks(this). This argument refers to the test class itself. Mockito retrieves any @Mock annotated fields in the test class and creates a mock instance. It then retrieves any @InjectMocks-annotated fields and injects mock objects into them.
We can verify whether the mock injection worked or not by inspecting variables of RegistrationService.
The above image shows that the registration service uses a mock address retriever.
Set expection
- On line number 20 we are setting expection using when(...).thenReturn(...) pattern. It can be translated to "when addressRetriever.retrieve() methods gets called then return a mock adddress".
Mocking Exception Throwing using Mockito
Handling and dealing with exceptions is a crucial part of any application. Let's extend our use case with one negative case. AddressRetriver throws InvalidCoordinatesException if latitude and longitude are incorrect. We will write a test case to cover the exception case.
Test Case for Exception Our mock was already set. We need to set the correct expectation in our test case.
Set expection
For exceptin the pattern is when(...).thenThrows(...).
Code Explanation
- on line number 5 we are setting exception using when(...).thenThrows(...) pattern. It can be translated to when the address is retrieved. retrieve gets called, then throw invalidCoordinatesException.
- On line 10, we are asserting an exception when registrationService.register gets called using junit5 assertThrows.
Unit Testing in Spring Boot Project using Mockito
Spring boot provides extremely good support for writing unit and integration tests using mockito and mock bean.
A typical three-layer architecture contains a presentation, service, and data layer. Spring boot testing framework provides test slices to unit test each layer independently of the other.
Spring Boot Test Slices
- Presentation layer - @WebMvcTest slice helps write unit test cases for controllers without spinning up the web container.
- Service layer - No need to slice here. Basic mocking and mock bean are good enough for the service layer.
- Data layer - @DataJpaTest slice helps write a unit test case for repositories.
- Integration test - @SpringBootTest helps write integration tests involving all the layers.
Using the spring boot way, let's modify the same test for RegistrationService.
Code explanation
- On line number 01 tells JUnit framework to test with spring runner.
- On line 02, we are creating the required bean to test and mock in the spring's application context.
- On line number 05-06, we are autowiring the RegistrationServicebean
- On lines 08-09, we are mocking the AddressRetriever bean using spring boot-specific @MockBean annotation. It Injects a mock AddressRetriever bean into RegistrationService.
Everything else is the same in the test case. The only difference from mockito-specific test cases is how we set up mock using spring boot.
@MockBean is spring boot annotation, not mockito.
Mocking Rest APIs using Spring Boot
Spring boot provides a @WebMvcTest slice to test only the web layer. Therefore applying this annotation instantiates only the web layer of the application. We can test the web layer without spinning up the web container.
Let's code it. We have a UserController in our application which contains an API to return users based on the ID.
UserController
The corresponding test case code will be:
Running the test case will print the request-response on the console.
Code explanation
- On line 01, we are marking our test with the @WebMvcTest annotation and specifying the controller under test.
- On lines 04-05, we are autowiring MockMvc. It's used to make HTTP requests to controller APIs
- On line 16, we are invoking API /user/1 with assertions 1) HTTP status 200 OK; 2) Response contains "Bob " in the body.
Conclusions
- Developers are supposed to write unit test cases for the functionality they are working on.
- Unit test cases should be contained within the application. It should not make any network call.
- Junit and mockito are popular frameworks that help write a unit test case.
- Mockito helps in mocking external system dependency.
- Mockito provides when(...).thenReturn(...) pattern to test positive scenario.
- Mockito provides when(...).thenThrows(...) pattern to test exception scenario.
- Spring boot provides test slices to write test cases for individual layers of three-layer architecture.