0

[Java Backend Zero to Hello] BÀI 7.2: MOCKITO

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 7: Testing & DevOps Cơ Bản | Bài 72/86


BÀI 7.2: MOCKITO

Mục tiêu

  • Sử dụng Mockito để mock dependency
  • Viết test với Mock, Stub, Spy
  • Verify behavior
  • Sử dụng với Spring Boot

1. MOCKITO LÀ GÌ?

Mockito là framework mock phổ biến nhất cho Java, giúp:

  • Tạo mock object cho dependency
  • Stub hành vi của mock
  • Verify method được gọi

2. DEPENDENCY

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
</dependency>

Hoặc dùng spring-boot-starter-test (bao gồm sẵn).


3. TẠO MOCK

3.1 @Mock

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void shouldFindUser() {
        // Stub
        when(userRepository.findById(1L))
            .thenReturn(Optional.of(new User(1L, "An")));

        // Act
        User user = userService.findById(1L);

        // Assert
        assertThat(user.getName()).isEqualTo("An");
    }
}

3.2 mock() static

UserRepository mock = mock(UserRepository.class);

4. STUB

4.1 thenReturn

when(userRepository.findById(1L))
    .thenReturn(Optional.of(new User(1L, "An")));

when(userRepository.findById(999L))
    .thenReturn(Optional.empty());

4.2 thenThrow

when(userRepository.findById(1L))
    .thenThrow(new RuntimeException("DB error"));

4.3 thenAnswer

when(userRepository.save(any(User.class)))
    .thenAnswer(invocation -> {
        User user = invocation.getArgument(0);
        user.setId(1L);
        return user;
    });

4.4 Chaining

when(userRepository.findById(1L))
    .thenReturn(Optional.of(user1))
    .thenReturn(Optional.of(user2))
    .thenThrow(new RuntimeException("Not found"));

5. ARGUMENT MATCHERS

// any()
when(userRepository.findById(any(Long.class)))
    .thenReturn(Optional.of(user));

// anyString()
when(userRepository.findByEmail(anyString()))
    .thenReturn(Optional.of(user));

// eq()
when(userRepository.findById(eq(1L)))
    .thenReturn(Optional.of(user));

// isNull / notNull
when(userRepository.findById(isNull()))
    .thenThrow(new IllegalArgumentException());

// Custom matcher
when(userRepository.findByName(argThat(name -> name.startsWith("A"))))
    .thenReturn(users);

6. VERIFY

6.1 Verify method được gọi

userService.create(request);

verify(userRepository).save(any(User.class));
verify(userRepository, times(1)).save(any(User.class));
verify(userRepository, never()).delete(any(User.class));
verify(userRepository, atLeastOnce()).findAll();

6.2 Verify với argument

verify(userRepository).findById(1L);
verify(userRepository).findById(eq(1L));
verify(userRepository).save(argThat(user -> user.getName().equals("An")));

6.3 Verify thứ tự

InOrder inOrder = inOrder(userRepository, emailService);
inOrder.verify(userRepository).save(user);
inOrder.verify(emailService).sendWelcome(user);

6.4 Verify không có interaction khác

verifyNoMoreInteractions(userRepository);
verifyNoInteractions(emailService);

7. SPY

Spy wrap một object thật, cho phép verify mà vẫn gọi method thật.

@Spy
private List<String> list = new ArrayList<>();

@Test
void shouldAddToList() {
    list.add("An");
    list.add("Bình");

    assertThat(list).hasSize(2);
    verify(list).add("An");
}

8. @CAPTOR

Bắt argument được truyền vào mock.

@Captor
private ArgumentCaptor<User> userCaptor;

@Test
void shouldSaveUser() {
    userService.create(request);

    verify(userRepository).save(userCaptor.capture());
    User saved = userCaptor.getValue();

    assertThat(saved.getName()).isEqualTo("An");
}

9. @INJECTMOCKS

Tự động inject mock vào object cần test.

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @InjectMocks
    private UserService userService;
}

10. VÍ DỤ HOÀN CHỈNH

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @InjectMocks
    private UserService userService;

    @Test
    void shouldCreateUser() {
        // Arrange
        CreateUserRequest request = new CreateUserRequest("An", "an@example.com", 25);
        User savedUser = new User(1L, "An", "an@example.com", 25);

        when(userRepository.existsByEmail("an@example.com")).thenReturn(false);
        when(userRepository.save(any(User.class))).thenReturn(savedUser);

        // Act
        UserResponse response = userService.create(request);

        // Assert
        assertThat(response.getId()).isEqualTo(1L);
        assertThat(response.getName()).isEqualTo("An");

        // Verify
        verify(userRepository).existsByEmail("an@example.com");
        verify(userRepository).save(any(User.class));
        verify(emailService).sendWelcome(any(User.class));
    }

    @Test
    void shouldThrowWhenEmailExists() {
        CreateUserRequest request = new CreateUserRequest("An", "an@example.com", 25);

        when(userRepository.existsByEmail("an@example.com")).thenReturn(true);

        assertThatThrownBy(() -> userService.create(request))
            .isInstanceOf(DuplicateException.class)
            .hasMessageContaining("Email already exists");

        verify(userRepository, never()).save(any(User.class));
    }

    @Test
    void shouldFindUser() {
        User user = new User(1L, "An", "an@example.com", 25);
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        UserResponse response = userService.findById(1L);

        assertThat(response.getName()).isEqualTo("An");
    }

    @Test
    void shouldThrowWhenUserNotFound() {
        when(userRepository.findById(999L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> userService.findById(999L))
            .isInstanceOf(NotFoundException.class);
    }
}

11. BDD STYLE

import static org.mockito.BDDMockito.*;

@Test
void shouldCreateUser() {
    // Given
    given(userRepository.existsByEmail("an@example.com")).willReturn(false);
    given(userRepository.save(any(User.class))).willReturn(savedUser);

    // When
    UserResponse response = userService.create(request);

    // Then
    assertThat(response.getName()).isEqualTo("An");
    then(userRepository).should().save(any(User.class));
}

12. BÀI TẬP THỰC HÀNH

Bài 1: Mock Repository

Test UserService với mock UserRepository.

Bài 2: Verify Behavior

Verify các method được gọi đúng.

Bài 3: ArgumentCaptor

Bắt argument và kiểm tra giá trị.


13. TÓM TẮT

Khái niệm Mô tả
@Mock Tạo mock object
@InjectMocks Inject mock vào object
@Spy Wrap object thật
when().thenReturn() Stub return value
when().thenThrow() Stub exception
verify() Verify method được gọi
ArgumentCaptor Bắt argument
BDD Style Given-When-Then

Bài tiếp theo: 7.3 Integration Test


🧭 Điều Hướng Series

⬅️ Bài trước: BÀI 7.1: UNIT TEST VỚI JUNIT 5

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 7.3: INTEGRATION TEST


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í