0

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

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 7: Testing & DevOps 📖 Nội dung: BÀI 7.2: MOCKITO 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


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í