Kết hợp Mockito với JUnit 5 Extension model
Bài đăng này đã không được cập nhật trong 6 năm
Bài viết này mình tìm hiểu và trình bày cách kết hợp Mokito với JUnit 5 extension model. Các bạn có thể đọc thêm về JUnit 5 extension model mình đã trình bày ở bài viết trước. Mình sẽ trình bày cách để tạo một lớp mở rộng sẽ tự động tạo các mock object cho bất kì thuộc tính hay tham số hàm của class bằng cách sử dụng chú thích @Mock.
1. Maven Dependencies
Chúng ta khai báo để sử dụng thư viện JUnit 5 và Mockito trong file pom.xml
như sau.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.11.0</version>
<scope>test</scope>
</dependency>
Một chú ý nhỏ là junit-jupiter-engine là thư viện chính của JUnit 5, và junit-platform-launcher được sử dụng với Maven plugin và IDE.
Giờ ta cấu hình plugin Maven Surefire để chạy các class test sử dụng Junit flatform mới.
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</plugin>
Nếu IDE của bạn không hỗ trợ JUnit 5 thì hãy cấu hình JUnit vintage để có thể sử dụng JUnit 5.
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.1</version>
<scope>test</scope>
</dependency>
2. Mockito extension
Hiện nay chưa có bản JUnit 5 extension chính thức nào từ Mockito, vì vậy chúng ta sẽ sử dụng bản mặc định được cung cấp bởi JUnit.
Mở rộng này thực thi interface TestInstancePostProcessor
để tạo các mock object cho các thuộc tính lớp, và thực thi interface ParameterResolver
để tạo các mock object cho các tham số hàm.
public class MockitoExtension
implements TestInstancePostProcessor, ParameterResolver {
@Override
public void postProcessTestInstance(Object testInstance,
ExtensionContext context) {
MockitoAnnotations.initMocks(testInstance);
}
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return
parameterContext.getParameter().isAnnotationPresent(Mock.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return getMock(parameterContext.getParameter(), extensionContext);
}
private Object getMock(
Parameter parameter, ExtensionContext extensionContext) {
Class<?> mockType = parameter.getType();
Store mocks = extensionContext.getStore(Namespace.create(
MockitoExtension.class, mockType));
String mockName = getMockName(parameter);
if (mockName != null) {
return mocks.getOrComputeIfAbsent(
mockName, key -> mock(mockType, mockName));
}
else {
return mocks.getOrComputeIfAbsent(
mockType.getCanonicalName(), key -> mock(mockType));
}
}
private String getMockName(Parameter parameter) {
String explicitMockName = parameter.getAnnotation(Mock.class)
.name().trim();
if (!explicitMockName.isEmpty()) {
return explicitMockName;
}
else if (parameter.isNamePresent()) {
return parameter.getName();
}
return null;
}
}
Chúng ta cùng xem qua hai method trong lớp mở rộng trên.
- Method
postProcessTestInstance
: method này sẽ khởi tạo các mock object cho toàn bộ các thuộc tính của các lớp test. - Method
supportsParameter
: method này ngầm thông báo cho JUnit rằng lớp mở rộng có thể đảm nhiệm tham số của hàm nếu như nó được chú thích bởi@Mock
. - Method
resolveParameter
: method này khởi tạo mock object cho tham số được yêu cầu.
3. Class test
Chúng ta hãy viết lớp test có kèm theo các Mockito mở rộng.
@ExtendWith(MockitoExtension.class)
@RunWith(JUnitPlatform.class)
public class UserServiceTest {
UserService userService;
//...
}
Chúng ta có thể sử dụng chú thích @Mock
để "tiêm" một mock object cho mỗi biến được khởi tạo và chúng ta có thể sử dụng chúng mọi nơi trong class test của ta.
@Mock UserRepository userRepository;
Giờ chúng ta có thể "tiêm" mock object đã khởi tạo cho các tham số hàm.
@BeforeEach
void init(@Mock SettingRepository settingRepository,
@Mock MailClient mailClient) {
userService = new DefaultUserService(
userRepository, settingRepository, mailClient);
when(settingRepository.getUserMinAge()).thenReturn(10);
when(settingRepository.getUserNameMinLength()).thenReturn(4);
when(userRepository.isUsernameAlreadyExists(any(String.class)))
.thenReturn(false);
}
Chúng ta vẫn có thể "tiêm" mock object cho các tham số hàm của class test.
@Test
void givenValidUser_whenSaveUser_thenSucceed(@Mock MailClient mailClient) {
user = new User("Jerry", 12);
when(userRepository.insert(any(User.class))).then(new Answer<User>() {
int sequence = 1;
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
User user = (User) invocation.getArgument(0);
user.setId(sequence++);
return user;
}
});
User insertedUser = userService.register(user);
verify(userRepository).insert(user);
Assertions.assertNotNull(user.getId());
verify(mailClient).sendUserRegistrationMail(insertedUser);
}
Lưu ý rằng mock object MailClient
chúng ta "tiêm" cho tham số hàm test givenValidUser_whenSaveUser_thenSucceed
sẽ giống với mock object ở hàm init
trên.
Tài liệu tham khảo
All rights reserved