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:

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


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));

  }