Test w/ Mockito (Java)
-1. Introduction
This page is about Mockito.
For JUnit 5, see > https://sites.google.com/site/pawneecity/java-development/test-w-junit5-java
OpenMocks, clean-up, etc (makes unnecessary MockitoAnnotations.openMocks)
JUnit 5 > @ExtendWith(MockitoExtension.class)
JUnit 4 > @RunWith(MockitoJUnitRunner.class)
-1.1. Compatibility matrix
Mockito JDK
5 >=11
4 <=8
2 =6
0. References
Mockito 5 Supports Mocking Constructors, Static Methods and Final Classes Out of the Box
https://www.infoq.com/news/2023/01/mockito-5/
1. TestMockingIssueAnswer
package edu.cou.myapp;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import lombok.extern.slf4j.Slf4j;
/**
*/
@Slf4j
public class TestMockingIssueAnswer implements Answer<Object> {
private static final Gson GSON = new Gson();
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String msg = buildMockingIssueMessage(invocation);
//
if (log.isDebugEnabled()) {
// Not usually needed, since the WrongMethodTypeException message is always printed
log.info(msg);
}
throw new java.lang.invoke.WrongMethodTypeException(msg);
}
/*-
* It build a mocking issue message.
* @param invocation
* @return The mocking issue message
*/
private String buildMockingIssueMessage(InvocationOnMock invocation) {
String msg;
if (invocation == null) {
msg = "********MOCKING:Probably method mocking failed********";
} else if (invocation.getMethod() == null) {
Object mock = invocation.getMock();
msg = String.format("********MOCKING:Not stubbed on %s********", serializeOrClassName(mock));
} else {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
msg = String.format(
"********MOCKING:Method %s not stubbed on %s, called with arguments %s********",
invocation.getMethod().getName(), serializeOrClassName(mock), GSON.toJson(args));
}
return msg;
}
/*-
* Serialize mock
* @param mock Either the serialized object if possible, or its class name
*/
private String serializeOrClassName(final Object mock) {
String ret;
try {
ret = GSON.toJson(mock);
} catch (UnsupportedOperationException e) {
// DOC. 'mock' is not serializale, see: https://stackoverflow.com/a/41937577/1323562
ret = mock.getClass().getName();
}
return ret;
}
}
2. Mockito explained
2.1. @Mock & @MockBean
Used for creating and injecting mocked instances. We don't create real objects, rather ask mockito to create a mock for the class.
Spring Boot @MockBean is similar to @Mock but with Spring support:
If the test doesn't need any dependencies from the Spring Boot container then @Mock shall be used. It's fast and favors the isolation of the tested component
If the test needs to rely on the Spring Boot container and we want also to add or mock one of the container beans then @MockBean is the preferred way to add mocks
2.2. @Spy & @SpyBean
For instantiating a class, via its default constructor, and spy on that real object.
Note: For spying any object, use instead: Mockito.spy(theObject).
A spy helps to call all the normal methods of the object while still tracking every interaction, just as we would with a mock.
@SpyBean can be used to apply Mockito spies to a Spring ApplicationContext.
2.3. @Captor
Used for creating and ArgumentCaptor instance which is used to capture method argument values for further assertions. Mockito verifies argument values using the equals() method of argument class.
2.4. @InjectMocks
Needed for creating the object of the class to be tested and then insert its dependencies (both @Mock and @Spy).
Warning: If an injection is not provided by either @Mock or @Spy then it'd be null.
2.5. Mockito.mock
Allows to create a mock object of a class or an interface.
2.6. Mockito.verify
Used for verifying that a method with expected arguments is invoked the specified number of times.
3. Examples
3.0. Mockito Argument Matchers
any(), any(Object.class), any(Object[].class) - the array one allows to match any number of arguments of the specified type
anyInt()
anyString()
aryEq - equality of arrays
eq() - specific value
isNull() - match null. Only Java >= 8 & Mockito >= 2
same(obj) - checks that the argument refers to the same instance as obj, such that arg == obj
3.1. Dynamic response depending on input
Get a mock of the class
log.info("#### Mocking myclassTest");
final MyClass c = Mockito.mock(MyClass.class,
new TestMockingIssueAnswer());
Mock the method
log.info("#### Mocking myclassTest.getThirdsByIdpList");
doAnswer(i -> fakeResponseGetThirdsByIdpList( (ArrayOfInt) i.getArguments()[0] )).when(c).getThirdsByIdpList(Mockito.any());
The lambda function used in the method mock
private ArrayOfThirdReducedVO fakeResponseGetThirdsByIdpList(ArrayOfInt codes) {
List<Integer> ins = codes.getInt();
ArrayOfThirdReducedVO ret = new ArrayOfThirdReducedVO();
for (Integer idpx : ins) {
ThirdReducedVO trx = new ThirdReducedVO();
trx.setAttribute1("A1-"+idpx);
trx.setAttribute2("A2-"+idpx);
ret.getThirdReducedVO().add(trx);
}
return ret;
}
3.2. doCallRealMethod, doNothing, doReturn
Sample doNothing:
doNothing().when(c).setFinalWork(Mockito.any(), Mockito.any(), Mockito.any());
Sample docCallRealMethod:
doCallRealMethod().when(environmentHelperMock).getEnvironmentMap();
Sample doReturn:
doReturn("Bearer f4k3").when(this.httpServletReqMock).getHeader("Authorization");
3.3. @Mock, @Spy, @InjectMocks & Mockito.verify (using JUnit5)
NotifService.java
@AllArgsConstructor(onConstructor_ = { @Inject })
@Slf4j
@Service
public class NotifService {
private NotifEnrollmentVeteranRepo notifEnrollmentVeteranRepo;
private NotifMilestoneService notifMilestoneService;
...
}
NotifServiceTest.java
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { App.class })
class NotifServiceTest {
/*- Need to be injected into notifService */
@Spy
private NotifEnrollmentVeteranRepo notifEnrollmentVeteranRepo;
@Mock
private NotifMilestoneService notifMilestoneService;
@Spy
@InjectMocks
private NotifService notifService;
private AutoCloseable closeable;
@BeforeEach
void beforeEach() {
this.closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void afterEach() throws Exception {
this.closeable.close();
}
@Test
public void startNotifTest() {
Mockito.doReturn(getDummyCurriculumConfigList()).when(curriculumDurationConfigService)
.findAll();
//
Set<AcademicRecord> ars = new HashSet<>();
AcademicRecord iniGatCodeKO = getDummyAcademicRecord(1L, "32423", "20184", null, null, null);
ars.add(iniGatCodeKO);
AcademicRecord opCalCodeKO = getDummyAcademicRecord(2L, "23", "20201", null, null, null);
ars.add(opCalCodeKO);
opCalCodeKO = getDummyAcademicRecord(3L, "8", null, null, null, null);
ars.add(opCalCodeKO);
AcademicRecord academicRecordOK = getDummyAcademicRecord(4L, "8", "20181", null, null, null);
academicRecordOK.setPersonCode(123L);
ars.add(academicRecordOK);
AcademicRecord alreadyHasNotif = getDummyAcademicRecord(5L, "8", "20181", null, null, null);
ars.add(alreadyHasNotif);
Mockito.doReturn(ars).when(academicManagementService)
.getAcademicRecordsInAorCAndNotIdentityValidated();
// Enrollments not cancelled for ar=4
Set<Enrollment> es4 = new HashSet<>();
es4.add(new Enrollment(new EnrollmentId(4L, "20181", 10L)));
Mockito.doReturn(es4).when(academicManagementService).getEnrollmentsNotCanceled(4L);
//
this.notifService.startNotificationVeterans();
Mockito.verify(notifService).notifAndPersistVeteran2(123L, "1", new EnrollmentId(4L, "20181",
10L));
}
}
3.4. @MockBean example
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { App.class })
class CalendarControllerTest {
@MockBean
private CalendarRepo calendarRepo;
@Inject
private CalendarController calendarController;
private AutoCloseable closeable;
@BeforeEach
void beforeEach() {
this.closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void afterEach() throws Exception {
this.closeable.close();
}
/** */
@Test
void getCalendarsTest() {
List<Object[]> objectList = new ArrayList<>();
Object[] ob = new Object[2];
ob[0] = "20221";
ob[1] = "2022/23-1";
objectList.add(ob);
doReturn(objectList).when(calendarRepo).getCalendarDetail(Mockito.anyString(), Mockito.anyString());
ResponseEntity<Set<CalendarDetail>> res = calendarController.getCalendar("en", "20212");
assertNotNull(res);
var body = res.getBody();
assertNotNull(res.getBody());
assertEquals(1, body.size());
var elem0 = body.iterator().next();
assertEquals("20221", elem0.getId());
assertEquals("2022/23-1", elem0.getNameShort());
}
}
3.5. @Captor example
@Mock
HashMap<String, Integer> hashMap;
@Captor
ArgumentCaptor<String> keyCaptor;
@Captor
ArgumentCaptor<Integer> valueCaptor;
@Test
public void saveTest()
{
hashMap.put("A", 10);
Mockito.verify(hashMap).put(keyCaptor.capture(), valueCaptor.capture());
assertEquals("A", keyCaptor.getValue());
assertEquals(new Integer(10), valueCaptor.getValue());
}
3.6. HttpURLConnection openMocks example
Including openMocks, @BeforeAll, @AfterAll, @BeforeEach & @AfterEach.
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.doReturn;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Optional;
import org.apache.commons.io.input.CharSequenceInputStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
*/
class SerializationUtilsTest {
private static class SampleJson {
@SuppressWarnings("unused")
String myProperty;
}
@Mock
private HttpURLConnection httpURLConnection;
private AutoCloseable closeable;
@BeforeAll
private static void beforeAll() {
// sample
}
@AfterAll
private static void afterAll() {
// sample
}
@BeforeEach
void beforeEach() {
this.closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void afterEach() throws Exception {
this.closeable.close();
}
/**
* @throws Exception
*/
@Test
void jsonHttpTest() throws Exception {
InputStream is = new CharSequenceInputStream("{\"myProperty\":\"My value\"}", "UTF-8");
doReturn(is).when(this.httpURLConnection).getInputStream();
Optional<SampleJson> res = SerializationUtils.getObjectFromJSON(this.httpURLConnection,
SampleJson.class);
assertNotNull(res);
}
}
3.7. doThrow example
@Test
void userinfoHttpClientErrorExceptionTest() {
// mock
doThrow(HttpClientErrorException.class).when(this.restTemplate).exchange(Mockito.anyString(),
Mockito.any(), Mockito.any(), Mockito.eq(RespRestUserinfoServiceDTO.class));
//
Assertions.assertThrows(IllegalStateException.class, () -> {
this.oauthClientHelper.userinfo("Bearer " + TestK.PERSON_SAINTS_OT);
});
}
3.8. mock example
@Test
void obtainHttpErrorStreamBodyTest() throws IOException {
HttpURLConnection connection = mock(HttpURLConnection.class);
InputStream errorInputStream = new ByteArrayInputStream("ErrorContent".getBytes());
doReturn(errorInputStream).when(connection).getErrorStream();
assertEquals("ErrorContent", Userinfo.obtainHttpErrorStreamBody(connection));
}