How to test with mockito in Spring Boot

How to test with mockito in Spring Boot

You can use JUnit, mockito, assertJ if you have the following in gradle.build of Spring Boot.
I will write a test method to mock the repository class used in the service class.

Mocking classes with dependencies

Only classes with dependencies can be mocked. For example, in a configuration such as “Controller → Service → Repository,” you cannot mock the repository in a controller test. The service class is the only class that can be mocked in the controller. The service class can mock the repository interface.

The image is as follows.

class TestControllerTest() {
  @InjectMocks private TestController testController; // implementation under test
  @Mock private TestService testService; // Dependent Classes
  @Mock private CommonService commonService; // Dependent Classes
  @Mock private TestManager testManager; // Dependent Classes
  ~

Mocking the repository interface in service class tests

The DB is accessed via the repository class within a method of the service class.

DemoServiceTest.java

package jp.co.confrage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class DemoServiceTest {

  @Mock // Annotation for classes used in the test object
  private EmployeeRepository repository;

  @InjectMocks // Annotation for the class under test
  private DemoService service;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void サンプルテスト() {
    // RuntimeException is raised when findAll is executed
    when(repository.findAll()).thenThrow(new RuntimeException());
    List<Employee> list = service.selectEmployee();// Execute the method under test
    assertThat(list.size()).isEqualTo(3); // Use assertJ
  }
}

Since the service class is the target of the test, @InjectMocks is added.

Attach @Mock to the repositories (in dependencies) to be used in the service class methods. Only classes you want to mock in the service class should be marked with @Mock.

Mocking will not work correctly unless you do MockitoAnnotations.initMocks(this); in @Before.

In the test method, specify the method as the when argument. “thenThrow(new RuntimeException());” will throw a runtime exception. This allows you to transition to a CATCH clause. If you want to return a value without throwing, use thenReturn method.

The following are classes of service

package jp.co.confrage;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Service
public class DemoService {
  @Autowired
  EmployeeRepository empRepository;
  @RequestMapping(value = "/method", method = RequestMethod.GET)
  public List<Employee> selectEmployee() {
    List<Employee> emplist = new ArrayList<Employee>();
    try {
      emplist = empRepository.findAll();
    } catch(Exception e) {
      // Return in JSON with error status here.
    }
    return emplist;
  }
}

Note in when().thenThrow()

Methods with no arguments, such as findAll(), will thenThrow(), but if you have your own method and arguments are present, you must use any() or similar.

For example, the following code will not work correctly.

when(repository.findBySpec('xx')).thenThrow(new RuntimeException());

In this case, the argument is a String type, so it is written as follows

when(repository.findBySpec(anyString()).thenThrow(new RuntimeException());

anyString(). (anyString does not contain null).

If the argument is an object, use any().

import static org.mockito.ArgumentMatchers.*;

Just import the above statically.

@RunWith(MockitoJUnitRunner.class) Mocking with Annotation

Up to JUnit4, you can also mock by adding @RunWith(MockitoJUnitRunner.class) to the class.

The @Before part is no longer necessary.

@RunWith(MockitoJUnitRunner.class)
public class DemoServiceTest {

When~thenThrow may not be used.

I was trying to use when~thenThrow to throw, but it seems that when~thenThrow cannot be used for methods with a return value of void. I think the error message is “The method when(T) of type Mockito cannot be applied to the argument (void).

Instead, use doThrow~when, doReturn~when, doAnswer~when, and doNothing~when.

doNothing~when,doReturn~when,doThrow~when

The usage of doNothing, doReturn, and doThrow is as follows.

method use case
doNothing Used in methods with return value void
doReturn Used in methods other than return value void
doThrow Used in tests that raise exceptions
doNothing().when(repository).deleteByPk(); // No Argument Case
doReturn(Integer.valueOf(1)).when(repository).deleteByPk(Mockito.any()); // One argument case
doThrow(new RuntimeException()).when(repository).deleteByPk(Mockito.any(), Mockito.any()); // Two argument cases

The argument of when is an instance and chain that method outside of when, not inside of when.

I feel it is better to use doThrow~when in all test cases than when~thenThrow.

assert uses assertThatExceptionOfType.

assertThatExceptionOfType(XXException.class).isThrownBy(() -> xxUploader.upload(path, file);

Now the test is OK if XXException is raised when the upload method is executed.

Mocking private methods is not possible with mockito

With JMockito, you can mock private methods, but with mockito, you cannot mock private methods.

It seems that mockito’s policy is that if you use private methods, your code design is not good enough.

Difference between org.mockito.mock and org.mockito.spy

You can mock an instance of the class using the org.mockito.Mockito() method.

Mock PrivateKey. RSAPrivateKey.class is a concrete class that implements the PrivateKey interface.

PrivateKey pk = Mockito.mock(RSAPrivateKey.class);

You can mock the above, but the instance created by mock is fake.

On the other hand, the instance created by spy actually works.

@Test
void test() {
  List<String> spy = Mockito.spy(new LinkedList<>());
  doNothing().when(spy).add(0, "a"); // Disable only when adding a
  spy.add(0, "a"); // be ignored
  spy.add(0, "b");

  System.out.println(spy.size()); // 1
  spy.stream().forEach(System.out::println); // b
}

Test private methods with reflection in mockito

Mockito does not allow you to mock private methods, but it does allow you to test private methods.

Use getDeclaredMethod as follows

public void private_method_TEST() {
  Method method = XXCalculator.class.getDeclaredMethod("calc", BigDecimal.class, BigDecimal.class);
  method.setAccessible(true);
  String actual = (String)method.invoke(new XXCalculator(), new BigDecimal("1"), new BigDecimal("1"));
  assertThat(actual).isNull();
}

setAccessible(true). You can now test the private method (in the above, the calc method).

When coding in mockito, the following should be static imports.

org.mockito.ArgumentMatchers
org.mockito.Mockito

Check the number of times a method is called

You can test to see how many times a mocked method is called with verify.

Methods that do not have a return value should be tested with verify. Write the following

verify(mock instance, times(2)).findByPk();

The above confirms that the findByPk() method is called twice.

Sample test code.

@RunWith(MockitoJUnitRunner.class)
public class DemoServiceTest {
  @Mock // Annotation for classes used in the test object
  private EmployeeRepository repository;

  @InjectMocks // Annotation for the class under test
  private DemoService service;

  @Test
  public void sampletest() {
    List<Integer> list = new ArrayList<>();
    doReturn(list).when(repository).findByPk();
    service.selectEmployee();// Execute the method under test

    verify(repository, times(1)).findByPk(); // Make sure you've been called once.
  }
}

Please remove unnecessary stubbings or use ‘lenient’ strictness. More info: javadoc for UnnecessaryStubbingException class.

If you get this error, the error is that you are mocking an instance that you are not even using.

For an easy solution, change @RunWith(Mockito.JUnitRunner.class as follows.

@RunWith(MockitoJUnitRunner.Silent.class)

This will eliminate the error.

JUnit5 + mockito3.x

Add dependencies in build.gradle.

testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testImplementation 'org.mockito:mockito-core:3.5.10'
testImplementation 'org.mockito:mockito-junit-jupiter:3.5.10'

Grant @ExtendWith(MockitoExtension.class) as a class-level annotation. If you do not specify this, you cannot mock.

If you get the error “Please remove unnecessary stubbings or use ‘lenient’ strictness. More info: javadoc for UnnecessaryStubbingException class.” in JUnit5 If you get the error @MockitoSettings(strictness = Strictness.LENIENT) in the class level annotation, you can avoid it. (I have not tried this with JUnit4)

I checked with @mockito3.5.10 and it seems that MockitoAnnotations.initMocks method is deprecated.

@TestInstance(Lifecycle.PER_METHOD) is given as a class-level annotation.

If omitted, defaults to Lifecycle.PER_METHOD.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // If you want to specify the test order with the @Order annotation
@ExtendWith(MockitoExtension.class) // For JUnit+Mockito
@MockitoSettings(strictness = Strictness.LENIENT)
@TestInstance(Lifecycle.PER_METHOD) // Default is PER_METHOD
class SampleTest {
  @InjectMocks private DemoService service;
  @Mock private DemoRepository Repository;

  @BeforeAll
  static void initAll() {}

  @BeforeEach
  void init() {}

  @Test
  @Order(1) // Execute 1st.
  @DisplayName("update method test of update API service")
  void updateTest() {
    // ...
  }

  @Test
  @Order(2) // Second to execute.
  @DisplayName("Select method testing for acquisition API services")
  void updateTest() {
    // ...
  }

Mocking HttpServletRequest

MockHttpServletRequest class is provided in spring-test-x.x.x.RELEASE.jar.

MockHttpServletRequest req = new MockHttpServletRequest();

Mocking the getObjectContent method of the S3Object class

To mock the S3 getObject method, you need to mock the S3Object class getObjectContent method.

Convert a string to a byte array.

@InjectMocks service;
@Mock AmazonS3 s3;

@Test
@DisplayName("API TEST")
public void api_Test() throws UnsupportedEncodingException {

  byte[] data = "{ \"accessToken\": \"hoge\", \"key\":\"value\"}".getBytes("UTF-8");
  final S3ObjectInputStream stream =
    new S3ObjectInputStream(new ByteArrayInputStream(data), new HttpGet()); // org.apache.http.client.methods.HttpGet
  final S3Object s3Object = new S3Object();
  s3Object.setObjectContent(stream);
  doReturn(s3Object)
    .when(s3)
    .getObject(Mockito.anyString(), Mockito.anyString()); // org.mockito.Mockito
  // test
  service.xxx();
  // assert
}

You can mock an S3ObjectInputStream instance by setting it with setObjectContent.

If the argument of the getObject method is a GetObjectRequest instance, change the argument to one.

doReturn(s3Object) .when(s3) .getObject(Mockito.anyString(), Mockito.anyString());
↓
doReturn(s3) .when(s3) .getObject(Mockito.any());

Mocking Value Objects

Mock a VO with a getter and no setter, you can mock it with mockito or you can easily mock it by overriding the getter as follows.

Employee emp =
  new Employee() {
    @Override
    public Long getId() {
      return Long.valueOf(1);
    }
  };
Test Smell: Everything is mocked
This Test Smell is about a weakness in the design of tests, rather than in the code to be tested. It may sound obvious, ...

Mock LocalDateTime.now()

It seems to be possible to mock if mockito and Powermock are used together.

Testing methods of abstract classes

Abstract classes cannot be given the @InjectMock annotation.

When testing the methods of AbstractService, you can test each method as Mockito.mock(AbstractService.class, Mockito.CALLS_REAL_METHODS);.

@Test
void AbstractServiceMethodTest() {
  AbstractService abstractservice = Mockito.mock(AbstractService.class, Mockito.CALLS_REAL_METHODS);
  String result = abstractservice.method("123");
  assertThat(result).isEqualTo("1-2-3");
}

Abstract Class Fields

Abstract class fields must be injected with the .getClass().getSuperclass().getDeclaredField method.

Field field = abstractclass.getClass().getSuperclass().getDeclaredField("baseHeaders");
field.setAccessible(true); // grant access right
field.set(abstractclass, new HttpHeaders()); // Fields set

API request body

Create a request body of type Map<String, Object> in the API test.

Map<String, Object> body =
  (new ObjectMapper()) // com.fasterxml.jackson.databind.ObjectMapper
    .readValue(
      "{\"token\": \"xxx\", \"records\":{ \"fuga\": [\"0101\",\"0102\"]}}"
        .getBytes("UTF-8"),
      new TypeReference<Map<String, Object>>() {});

Returns different results depending on the number of method calls

The when~thenReturn method can be used to return different results depending on the number of calls: the first return value is BigDecimal.ZERO, the second return value is BigDecimal.ONE.

when(Instance. Methods(Mockito.any()))
.thenReturn(BigDecimal.ZERO, BigDecimal.ONE);

If you want to throw the first time and return the second time, then chain the methods like thenThrow().thenReturn().

when(Instance. Methods(Mokito.any()))
.thenThrow(new RuntimeException())
.thenReturn("test");

コメント

Discover more from 株式会社CONFRAGE ITソリューション事業部

Subscribe now to keep reading and get access to the full archive.

Continue reading

Copied title and URL