Junit and Mockito
March 16, 2022
Basic Example
There are three basic phases to writing unit tests. Mocking, Execution, Verification. Each of the steps are shown in the example below.
@RunWith(MockitoJunitRunner.class)public class ControllerTest {@MockAppService service;@MockUserRepository userRepo;@InjectMocksAppController controller;@Testpublic void testRetrieveMessage(){//Data prepString mockText = "hello!";RetrieveMessagesResponse expected = new RetrieveMessagesResponse(text);expected.setSuccess("SUCCEEDED");String input = "this is my message";//Mock - mock any internal dependencies that are decorated with @MockMockito.when(service.getMessage(any(String.class))).thenReturn(mockText);Mockito.when(userRepo.findById(any(Long.class))).thenReturn(1L);//Execution - execution the method under test.RetrieveMessagesResponse actual = controller.retrieveMessage(input);//VerificationassertEquals(expected, actual);}}
Exception testing example
To test exception handling, you can mock a thrown exception and then catch the exception (more flexibility to do verification) or just tell the test to expect the exception (less flexibility). Both ways are shown below.
@RunWith(MockitoJunitRunner.class)public class ControllerTest {@MockAppService service;@MockUserRepository userRepo;@InjectMocksAppController controller;@Testpublic void testExceptionFirstWay(){//Data prepString mockText = "hello!";String input = "this is my message";//Mock - force exception to be thrownMockito.when(service.getMessage(any(String.class))).thenThrow(new ServerException("can't find message"));//Execution and verificationtry {RetrieveMessagesResponse actual = controller.retrieveMessage(input);Assertions.fail("Expected server exception to be thrown, but was not").}catch (ServerException ex){assertEquals("can't find message", ex.getErrorMessage());}}@Test(expected = ServerException.class)public void testExceptionSecondWay(){//Data prepString mockText = "hello!";String input = "this is my message";//Mock - force exception to be thrownMockito.when(service.getMessage(any(String.class))).thenThrow(new ServerException("can't find message"));//Execution - will throw ServerException and is expected so test will passRetrieveMessagesResponse actual = controller.retrieveMessage(input);}}
Other things
Setting internal fields or testing private methods
To set internal fields, you can use Spring ReflectionTestUtils.setField(). This assumes you are using Spring, else probably can use power mock.
To call private methods, you can use powerMock Whitebox.invokeMethod().
ThenAnswer
When you want your mocking response to differ based on the arguments passed, you can use .thenAnswer to return the mock response.
Mockito.when(service.retrieveMessage(any(String.class), any(Integer.class))).thenAnswer(invocation -> {RetriveMessageResponse response = new RetriveMessageResponse();response.setText(invocation.getArgument(0)); //Set the first argument as text response.return response;})
Number of Times
You can verify the number of times a method was called. In this case, checking service.retrieveProduct()
got called twice.
Mockito.verify(service, times(2)).retrieveProduct(anyString());
Throwing Spring’s DataAccessException
DataAccessException is an abstract class meaning you can’t instantiate it directly. You find a subclass that can be instantiated or an anonymous class.
.thenThrow(throw new DataAccessException("failed") {})
Using a real object mapper - since most times it doesn’t need to be mocked
@MockObjectMapper mapper;Mockito.when(mapper.createObjectNode()).thenReturn(new ObjectMapper().createObjectNode());