0

Kết hợp Mockito với JUnit 5 Extension model

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí