Mockito
pom.xml
<!-- mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- cầu nối giữa Mockito và JUnit 5. -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
Simple Example
- UserDao Interface:
package com.example;
// Data Access Object (DAO) interface for User
public interface UserDao {
boolean createUser(String email);
}
- UserService Interface:
package com.example;
public interface UserService {
String createUser(String email);
}
- UserServiceImpl class implement:
package com.example;
class UserServiceImpl implements UserService {
private final UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public String createUser(String email) {
boolean created = userDao.createUser(email);
return created ? "SUCCESS" : "FAILED";
}
}
-
Cả 2 Interface: UserDao và UserService đều có một method là createUser tuy nhiên nó khác vai trò nhau, dù cùng "một việc" → vẫn cần tách theo vai trò
- UserDao sẽ làm việc với database, chịu trách nhiệm insert/update với DB, chỉ chạy câu lệnh SQL.
- UserService sẽ thực hiện xử lý logic (Có thể kiểm tra email, gửi mail, log, gọi service khác) → rồi gọi DAO → rồi xử lý kết quả.
-
Dùng interface như biến: Đây là nguyên tắc lập trình hướng interface – rất quan trọng trong Java. Viết code không phụ thuộc vào class cụ thể, giúp code dễ thay đổi, dễ test, dễ mở rộng
UserDao userDao = new MySQLUserDao(); // ✅ OK nếu MySQLUserDao implements UserDao
// ----------------------------------------------------
// trong code này UserDao sẽ được truyền từ bên ngoài vào
public class UserServiceImpl implements UserService {
private final UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
// ------
// Có class nào đó implement interface UserDao
UserDao dao = new MySQLUserDao();
// Truyền vào constructor
UserService service = new UserServiceImpl(dao);
- UserServiceImplTest:
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
@Mock
private UserDao userDao;
@InjectMocks
private UserServiceImpl userService;
@Test
void createUser_shouldReturnSuccess_whenUserCreatedSuccessfully() {
when(userDao.createUser("test@example.com")).thenReturn(true);
assertEquals("SUCCESS", userService.createUser("test@example.com"));
}
@Test
void createUser_shouldReturnFailed_whenUserAlreadyExists() {
when(userDao.createUser("existing@example.com")).thenReturn(false);
assertEquals("FAILED", userService.createUser("existing@example.com"));
}
}
-
@ExtendWith(MockitoExtension.class): Nói với JUnit 5 rằng: “Tôi muốn dùng Mockito trong file test này”
-
Tạo một object giả (mock) để thay cho class hoặc interface thật, Giúp bạn không cần implement UserDao, nhưng vẫn có thể gọi createUser():
@Mock
private UserDao userDao;
- Tạo object thật (UserServiceImpl), tự động inject các mock như userDao vào constructor hoặc field: InjectMocks tạo userService và inject mock userDao vào constructor
@InjectMocks
private UserServiceImpl userService;
// userService = new UserServiceImpl(userDao); // 👈 userDao là mock
- when(userDao.createUser("test@example.com")).thenReturn(true);: giả lập logic trả về true
Test Doubles in Unit Testing
-
Dummy Object:
- Là object chỉ để truyền cho đủ tham số, không dùng đến
- Không có annotation nào
- Tự nhận biết nó là dummy
- Mockito, JUnit không thể biết đó là dummy
-
Fake Object:
- Là class tự viết, có logic thật đơn giản
- Framework không tự nhận ra đó là "fake"
- Được sử dụng trong môi trường test, không phù hợp cho môi trường thật.
-
Stub Object:
- Stub là object được lập trình trả về một kết quả cố định khi gọi một method — không có logic phức tạp.
- Stub là cách dùng, Mock là loại object → Một mock có thể dùng để stub hoặc để verify
- when(userDao.createUser("abc@gmail.com")).thenReturn(true);: Đây là hành vi stub, nhưng object userDao vẫn là mock
-
Mock Object:
- Là object giả dùng để kiểm tra hành vi: có được gọi đúng không, với đúng tham số không.
- Có thể được framework nhận biết
- Dùng @Mock hoặc Mockito.mock(...)
- Có thể verify được hành vi: verify(...)
- Có thể stub: when(...).thenReturn(...)
-
Spy Object:
- Spy là bản lai giữa object thật và mock, nghĩa là nó gọi được method thật, nhưng vẫn có thể ghi đè 1 phần hành vi nếu muốn.
- Dùng @Spy hoặc spy(obj)
- Framework nhận biết rõ ràng
- Cho phép gọi method thật, nhưng vẫn có thể stub hoặc verify
Basic
Enable Mockito Annotations
@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {
...
}
// ---------------------
@Mock
- @Mock tự động tạo một đối tượng giả lập của List.
@Mock
List<String> mockedList;
@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
@DoNotMock
- Được sử dụng để chỉ ra rằng một lớp hoặc giao diện không nên bị mock trong quá trình kiểm thử.
import org.mockito.exceptions.misusing.DoNotMock;
@DoNotMock(reason = "Use a real instance instead")
public abstract class NotToMock {
// Lớp này không nên bị mock trong các kiểm thử.
}
@Spy
- Được sử dụng để theo dõi (spy) một đối tượng thật và có thể thay đổi hành vi của các phương thức cụ thể mà không ảnh hưởng đến các phương thức khác.
@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
List<String> spyList = Mockito.spy(new ArrayList<String>());
spyList.add("one");
spyList.add("two");
Mockito.verify(spyList).add("one");
Mockito.verify(spyList).add("two");
assertEquals(2, spyList.size());
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
}
// ------------------------------------------------------
@Spy
List<String> spiedList = new ArrayList<String>();
@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
spiedList.add("one");
spiedList.add("two");
Mockito.verify(spiedList).add("one");
Mockito.verify(spiedList).add("two");
assertEquals(2, spiedList.size());
Mockito.doReturn(100).when(spiedList).size();
assertEquals(100, spiedList.size());
}
@Captor
- Được sử dụng để tạo ra một ArgumentCaptor, giúp bắt các đối số truyền vào trong các phương thức gọi.
- @Captor tự động tạo một ArgumentCaptor, giúp việc kiểm tra đối số của phương thức dễ dàng hơn.
@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
List mockList = Mockito.mock(List.class);
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("one");
Mockito.verify(mockList).add(arg.capture());
assertEquals("one", arg.getValue());
}
// -------------------------------------------
@Mock
List mockedList;
@Captor
ArgumentCaptor<String> argCaptor;
@Test
public void whenUseCaptorAnnotation_thenTheSame() {
mockedList.add("one");
Mockito.verify(mockedList).add(argCaptor.capture());
assertEquals("one", argCaptor.getValue());
}
- Ví dụ:
// ---------------------capture() - getValue()
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Captor
ArgumentCaptor<String> captor;
@Test
public void testCaptureAndGetValue() {
// Gọi phương thức add() với đối số "Test"
mockList.add("Test");
// Bắt đối số được truyền vào phương thức add()
verify(mockList).add(captor.capture());
// Lấy giá trị của đối số đã được bắt
assertEquals("Test", captor.getValue());
}
}
// ------------------getAllValues()
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Captor
ArgumentCaptor<String> captor;
@Test
public void testGetAllValues() {
// Gọi phương thức add() nhiều lần với các đối số khác nhau
mockList.add("First");
mockList.add("Second");
// Bắt tất cả các đối số được truyền vào phương thức add()
verify(mockList, times(2)).add(captor.capture());
// Lấy tất cả các giá trị đã bắt được
assertEquals(2, captor.getAllValues().size());
assertEquals("First", captor.getAllValues().get(0));
assertEquals("Second", captor.getAllValues().get(1));
}
}
@InjectMocks
-
@InjectMocks được sử dụng để tự động chèn các đối tượng mock vào đối tượng được kiểm thử. Đây là một cách rất hữu ích để kiểm tra các lớp có sự phụ thuộc vào các đối tượng mock.
-
Trong khi kiểm thử, các lớp thường phụ thuộc vào các đối tượng khác (gọi là dependency) để thực hiện các hành động của chúng.
public class MyDictionary {
private Map<String, String> wordMap;
public MyDictionary() {
wordMap = new HashMap<>(); // Tạo map mới trong constructor
}
public void add(String word, String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(String word) {
return wordMap.get(word);
}
}
// ------------------------------------------------\
@Mock
Map<String, String> wordMap; // Tạo mock cho Map
@InjectMocks
MyDictionary dic = new MyDictionary(); // Tạo đối tượng cần kiểm thử và tự động chèn mock vào
@Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
// Khi gọi phương thức get trên mock, mock sẽ trả về "aMeaning"
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
// Kiểm tra phương thức getMeaning trong MyDictionary
assertEquals("aMeaning", dic.getMeaning("aWord"));
}
Assertion
- Kiểm tra rằng kết quả thực tế của phương thức kiểm thử khớp với kết quả kỳ vọng.
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
public class MethodTest {
@Test
public void testAddition() {
int result = 2 + 3;
assertEquals(5, result);
}
@Test
public void testPositiveCondition() {
int result = 5 + 5;
assertTrue(result > 0);
}
@Test
public void testNotNull() {
String str = "Hello, JUnit!";
assertNotNull(str);
}
@Test
public void testNull() {
String str = null;
assertNull(str);
}
// Phương thức test để kiểm tra ngoại lệ
@Test
public void testDivideByZero() {
Calculator calculator = new Calculator();
// Kiểm tra phương thức khi chia cho 0 có ném ra ArithmeticException không
ArithmeticException exception = assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0); // Phương thức chia cho 0
});
// Kiểm tra thông điệp của ngoại lệ
assertEquals("Cannot divide by zero", exception.getMessage());
}
// Phương thức test để kiểm tra mảng có giống nhau không
@Test
public void testArrayEquality() {
int[] expected = {1, 2, 3};
int[] actual = {1, 2, 3};
// Kiểm tra xem hai mảng có giống nhau không
assertArrayEquals(expected, actual);
}
// Phương thức test để kiểm tra danh sách (Iterable) có giống nhau không
@Test
public void testIterableEquality() {
List<String> expected = Arrays.asList("a", "b", "c");
List<String> actual = Arrays.asList("a", "b", "c");
assertIterableEquals(expected, actual);
}
// Phương thức test để kiểm tra ngoại lệ ném ra khi chia cho 0
@Test
public void testThrowException() {
// Kiểm tra xem khi chia cho 0 có ném ra ngoại lệ không
assertThrows(ArithmeticException.class, () -> {
int result = 10 / 0; // Chia cho 0 sẽ gây ra ArithmeticException
});
}
}
// Lớp Calculator để kiểm thử
class Calculator {
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return a / b;
}
}
Injecting a Mock Into a Spy
- Mockito không hỗ trợ tự động tiêm các mock vào đối tượng spy thông qua @InjectMocks, như trong trường hợp với @Mock.
- Cần phải tiêm (inject) các mock vào spy một cách thủ công.
import java.util.Map;
public class MyDictionary {
private Map<String, String> wordMap;
// Constructor để tiêm dependency
public MyDictionary(Map<String, String> wordMap) {
this.wordMap = wordMap;
}
// Phương thức thêm từ và nghĩa vào map
public void add(String word, String meaning) {
wordMap.put(word, meaning);
}
// Phương thức lấy nghĩa của từ
public String getMeaning(String word) {
return wordMap.get(word);
}
}
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyDictionaryTest {
@Mock
Map<String, String> wordMap; // Mock đối tượng Map
@Spy
MyDictionary spyDic; // Đối tượng MyDictionary sẽ được spy
// Trước khi chạy các test, khởi tạo các mock và spy
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this); // Khởi tạo Mockito annotations
// Tạo đối tượng spy của MyDictionary và tiêm mock vào thông qua constructor
spyDic = Mockito.spy(new MyDictionary(wordMap));
}
@Test
public void whenUseSpyAndMock_thenCorrect() {
// Định nghĩa hành vi mock, khi gọi wordMap.get("aWord") sẽ trả về "aMeaning"
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
// Kiểm tra phương thức getMeaning trong MyDictionary
String meaning = spyDic.getMeaning("aWord");
// Kiểm tra xem kết quả có đúng không
assertEquals("aMeaning", meaning);
}
}
Functions
when().thenReturn()
Mockito.any()
- Khớp bất kỳ đối số nào
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Test
public void testAnyMethod() {
// Stubbing phương thức add với bất kỳ đối số nào
when(mockList.add(any(String.class))).thenReturn(true);
// Kiểm tra
assertTrue(mockList.add("Test"));
assertTrue(mockList.add("Another Test"));
}
}
Mockito.doReturn().when()
-
Dùng cho spy để tránh lỗi NullPointerException
-
NullPointerException: là một loại lỗi trong Java xảy ra khi cố gắng thực hiện một thao tác trên một đối tượng có giá trị là null.
-
Ví dụ như với when(...).thenReturn(...) với Spy: nó sẽ gọi phương thức thật của đối tượng spy trước khi áp dụng hành vi giả lập (stubbing). Điều này có thể gây ra lỗi NullPointerException nếu phương thức thật đang stub có thao tác với các đối tượng hoặc dữ liệu có giá trị null hoặc chưa được khởi tạo đầy đủ.
-
Do đó sử dụng doReturn().when() với spy thay vì when(...).thenReturn(...). Lý do là doReturn().when() sẽ không gọi phương thức thực mà chỉ thay đổi hành vi của phương thức mà không thực thi bất kỳ logic nào bên trong phương thức đó.
import org.junit.jupiter.api.Test;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
public class SpyTest {
@Spy
OrderService orderService = new OrderService();
@Test
public void testCalculateTotal() {
// Khi gọi calculateTotal(), spy sẽ gọi phương thức thật
when(orderService.calculateTotal()).thenReturn(10); // Sẽ gây NPE nếu không khởi tạo 'items'
// Lỗi: NullPointerException vì items chưa được khởi tạo
System.out.println(orderService.calculateTotal());
}
}
// -------------------------------------------------------------------
import org.junit.jupiter.api.Test;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
public class SpyTest {
@Spy
OrderService orderService = new OrderService();
@Test
public void testCalculateTotal() {
// Đảm bảo không gọi phương thức thật, tránh NPE
doReturn(10).when(orderService).calculateTotal();
// Kiểm tra phương thức đã trả về giá trị giả lập
System.out.println(orderService.calculateTotal()); // Output: 10
}
}
Mockito.times()
- Kiểm tra số lần gọi một phương thức
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class TimesMethodTest {
@Mock
List<String> mockList;
@Test
public void testTimes() {
mockList.add("one");
mockList.add("two");
// Kiểm tra phương thức add() đã được gọi 2 lần
verify(mockList, times(2)).add(anyString());
}
}
Mockito.atLeast()
- Kiểm tra phương thức được gọi ít nhất một số lần
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class AtLeastMethodTest {
@Mock
List<String> mockList;
@Test
public void testAtLeast() {
mockList.add("one");
mockList.add("two");
// Kiểm tra phương thức add() đã được gọi ít nhất 1 lần
verify(mockList, atLeast(1)).add(anyString());
}
}
Mockito.atMost()
- Kiểm tra phương thức không được gọi quá một số lần
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Test
public void testAtMost() {
mockList.add("one");
mockList.add("two");
mockList.add("three");
// Kiểm tra phương thức add() không được gọi quá 3 lần
verify(mockList, atMost(3)).add(anyString());
}
}
Mockito.never()
// Kiểm tra phương thức không được gọi
verify(mockList, never()).add(anyString());
Mockito.timeout()
- Kiểm tra số lần gọi phương thức trong một khoảng thời gian nhất định
verify(mockList, timeout(100).times(1)).add(anyString());
Mockito.reset()
- Reset lại mock để sử dụng lại trong các kiểm thử khác
public void testReset() {
mockList.add("one");
// Reset lại mock, không còn tương tác nào
reset(mockList);
// Sau khi reset, mockList không giữ lại trạng thái cũ
verify(mockList, never()).add("one");
}
Mockito.verifyNoMoreInteractions()
- Kiểm tra không có tương tác nào khác ngoài các phương thức đã xác nhận
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Test
public void testNoMoreInteractions() {
mockList.add("one");
mockList.add("two");
// Kiểm tra rằng không có tương tác nào khác ngoài các phương thức add
verify(mockList).add("one");
verify(mockList).add("two");
verifyNoMoreInteractions(mockList);
}
}
Mockito.doThrow()
- Định nghĩa hành vi ném ngoại lệ khi phương thức của mock được gọi
- Mô phỏng hành vi ném ngoại lệ (exception) khi gọi phương thức trên đối tượng mock bằng cách sử dụng doThrow() trong Mockito. Cụ thể, trong ví dụ này, phương thức remove() của List sẽ ném ra một ngoại lệ UnsupportedOperationException khi được gọi.
package com.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class MethodTest {
@Mock
List<String> mockList;
@Test
public void testDoThrow() {
// Định nghĩa rằng khi gọi phương thức remove() trên mockList sẽ ném ngoại lệ
doThrow(new UnsupportedOperationException("Unsupported operation")).when(mockList).remove(anyInt());
// Kiểm tra ngoại lệ
UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, () -> {
mockList.remove(0);
});
assertEquals("Unsupported operation", thrown.getMessage());
}
}