- How to test with mockito in Spring Boot
- Mocking classes with dependencies
- Mocking the repository interface in service class tests
- Note in when().thenThrow()
- @RunWith(MockitoJUnitRunner.class) Mocking with Annotation
- doNothing~when,doReturn~when,doThrow~when
- Mocking private methods is not possible with mockito
- Difference between org.mockito.mock and org.mockito.spy
- Test private methods with reflection in mockito
- Check the number of times a method is called
- Please remove unnecessary stubbings or use ‘lenient’ strictness. More info: javadoc for UnnecessaryStubbingException class.
- JUnit5 + mockito3.x
- Mocking HttpServletRequest
- Mocking the getObjectContent method of the S3Object class
- Mocking Value Objects
- Mock LocalDateTime.now()
- Testing methods of abstract classes
- Abstract Class Fields
- API request body
- Returns different results depending on the number of method calls
- Related
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); } };
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");
コメント