Hướng dẫn Mock API để test với Mockito

Tóm tắt nhanh
Tìm hiểu cách sử dụng Mockito để mock API calls trong Flutter tests, giúp test nhanh và không phụ thuộc vào network.
Mục lục bài viết (45)
- Setup Mockito
- 1. Dependencies
- 2. Install
- Tạo Mock Classes
- 1. Define Abstract Class
- 2. Generate Mock
- 3. Run Build Runner
- Sử dụng Mock
- 1. Basic Mocking
- 2. Mock với Arguments
- 3. Mock Exceptions
- Advanced Mocking
- 1. Mock với any()
- 2. Mock với argThat()
- 3. Mock Multiple Calls
- Mock HTTP Client
- 1. Mock http.Client
- 2. Sử dụng trong Test
- Best Practices
- 1. Setup và Teardown
- 2. Verify Calls
- 3. Reset Mocks
- Mockito và mocktail: chọn cái nào
- Sinh lớp giả bằng build_runner
- Mock cái gì, và khi nào nên viết bản giả bằng tay
- when / thenReturn / thenAnswer: ba mức linh hoạt
- Bắt tham số và kiểm tra lời gọi
- Mock lỗi: đồng bộ và bất đồng bộ
- Mock Stream cho dữ liệu realtime
- Mock HTTP: MockClient và adapter giả cho Dio
- setUp, tearDown và rò rỉ giữa các test
- Mock thời gian, ID và số ngẫu nhiên
- Khi việc mock phản tác dụng
- Lỗi thường gặp với Mockito
- Câu hỏi thường gặp
- Có nên mock BuildContext không?
- Mockito có kiểm tra được getter và setter không?
- Làm sao kiểm tra thứ tự các lời gọi?
- Khi nào nên chạy test với dữ liệu thật thay vì mock?
- Ví dụ đầy đủ: test luồng thêm vào giỏ hàng
- Bốn câu hỏi trước khi viết một mock
- Khi nào cần mock trong widget test
- Kết luận
- Tóm tắt:
- Học Flutter bài bản theo lộ trình
Mockito là công cụ mạnh mẽ để mock dependencies trong Flutter tests. Bài viết này sẽ hướng dẫn bạn sử dụng Mockito để mock API calls.
Setup Mockito
1. Dependencies
[object Object]2. Install
[object Object]Tạo Mock Classes
1. Define Abstract Class
[object Object]2. Generate Mock
[object Object]3. Run Build Runner
[object Object]Sử dụng Mock
1. Basic Mocking
[object Object]2. Mock với Arguments
[object Object]3. Mock Exceptions
[object Object]Advanced Mocking
1. Mock với any()
[object Object]2. Mock với argThat()
[object Object]3. Mock Multiple Calls
[object Object]Mock HTTP Client
1. Mock http.Client
[object Object]2. Sử dụng trong Test
[object Object]Best Practices
1. Setup và Teardown
[object Object]2. Verify Calls
[object Object]3. Reset Mocks
[object Object]Mockito và mocktail: chọn cái nào
Hai thư viện này giải quyết cùng một việc nhưng khác cách tiếp cận:
mockitodùng chú thích@GenerateMocksvà trình sinh mã. Bạn chạydart run build_runner buildđể sinh lớp giả. Ưu điểm: kiểm tra kiểu chặt chẽ, phát hiện sớm khi interface thay đổi. Nhược điểm: thêm một bước sinh mã vào quy trình, và mỗi lần đổi interface phải chạy lại.mocktailkhông cần sinh mã: bạn kế thừaMockvà khai báo những hàm cần dùng. Ưu điểm: không có bước sinh mã, đọc file test là hiểu ngay. Nhược điểm: không có kiểm tra kiểu cho các hàm bạn quên khai báo.
Cách chọn thực dụng:
- Dự án nhiều người, interface hay thay đổi, muốn compiler bắt lỗi sớm →
mockito. - Dự án nhỏ hoặc bạn muốn bộ test đơn giản, ít bước →
mocktail. - Đã dùng
mockitotrong dự án → giữ nguyên, đừng trộn hai thư viện vì cách viết khác nhau sẽ gây nhầm lẫn khi review.
Lưu ý quan trọng: không cần công cụ mock cho mọi thứ. Với những thứ đơn giản như thời gian, ID, hoặc một bảng dữ liệu cố định, viết một lớp giả bằng tay thường ngắn hơn và dễ đọc hơn nhiều.
Sinh lớp giả bằng build_runner
Với mockito, quy trình gồm ba bước, và bước thứ ba là bước hay bị quên:
[object Object]Nếu bạn quen làm việc liên tục, có thể chạy chế độ theo dõi:
[object Object]Hai lỗi thường gặp:
- Quên
--delete-conflicting-outputs→ sinh mã báo lỗi vì file cũ đang tồn tại. - Sửa interface nhưng quên sinh lại → test lỗi theo kiểu khó hiểu như "hàm không tồn tại" hoặc hành vi cũ vẫn còn. Hãy đưa
build_runnervào quy trình trước khi chạy test.
Lưu ý cuối: file sinh ra (*.mocks.dart) nên được commit cùng mã nguồn trong dự án ứng dụng. Nhờ vậy người khác clone về và chạy test ngay, không phải cài thêm bước sinh mã.
Mock cái gì, và khi nào nên viết bản giả bằng tay
Nguyên tắc: mock ở ranh giới với thế giới bên ngoài, không mock chính logic của bạn.
Nên mock:
- Lớp gọi API, lớp truy cập cơ sở dữ liệu, lớp lưu trữ cục bộ.
- Dịch vụ bên thứ ba: thanh toán, thông báo, phân tích.
- Thời gian, ID, số ngẫu nhiên — những nguồn không xác định.
Không nên mock:
- Các lớp thuần logic (tính toán, chuyển đổi dữ liệu) — hãy gọi thật, vì đó chính là thứ bạn muốn kiểm tra.
- Các mô hình dữ liệu — hãy tạo đối tượng thật.
Khi dữ liệu giả đơn giản và dùng lại nhiều lần, viết một lớp giả bằng tay thường tốt hơn:
[object Object]Lớp giả này chỉ vài dòng, không cần sinh mã, và quan trọng hơn: nó giữ trạng thái, nên bạn kiểm tra được cả luồng "gọi API rồi kiểm tra dữ liệu lưu". Với mock thuần, bạn phải dựng nhiều when lồng nhau để mô phỏng cùng một luồng — code test sẽ dài hơn nhiều so với code đang được test.
when / thenReturn / thenAnswer: ba mức linh hoạt
[object Object]Điểm cần chú ý: thenAnswer nhận cả tham số, nên đây là cách duy nhất để mô phỏng hành vi phụ thuộc đầu vào. Với những hàm có nhiều tham số, hãy dùng invocation.namedArguments['tenThamSo'] để lấy đúng giá trị — cách này đọc dễ hơn dùng vị trí.
Một chi tiết rất hay gây lỗi: nếu hàm trả về Future, bên trong thenAnswer bạn phải trả về Future:
[object Object]Bắt tham số và kiểm tra lời gọi
Đây là phần biến mock từ "trả về dữ liệu giả" thành "kiểm tra hợp đồng".
[object Object]Với mockito, argThat cho phép viết điều kiện phức tạp hơn:
[object Object]Và khi cần kiểm tra giá trị thật được truyền vào (thay vì khẳng định ngay), dùng captureAny để lấy lại sau:
[object Object]Cách này đặc biệt hữu ích khi giá trị được tạo bên trong service và bạn muốn kiểm tra nó khớp với dữ liệu đầu vào — ví dụ tổng tiền được tính đúng.
Mock lỗi: đồng bộ và bất đồng bộ
Kiểm tra nhánh lỗi là phần quan trọng nhất, và ở đây có một cạm bẫy về cú pháp:
[object Object]Nếu bạn dùng thenThrow cho một hàm trả Future và trong code lại await nó, ngoại lệ sẽ được ném ra ngay tại thời điểm gọi — khác với hành vi thật của lỗi mạng. Cách viết Future.error mô phỏng đúng hơn.
Về phía test, hãy dùng throwsA với kiểu cụ thể thay vì chỉ khẳng định "có lỗi":
[object Object]Ba kiểu ngoại lệ nên có test riêng cho mỗi service: lỗi mạng, lỗi 4xx (dữ liệu sai, chưa xác thực), và lỗi 5xx (lỗi phía máy chủ). Ba nhánh này dẫn tới ba cách xử lý khác nhau ở giao diện.
Mock Stream cho dữ liệu realtime
Với dữ liệu theo thời gian thực, mock cần trả về Stream và test phải kiểm tra cả chuỗi sự kiện:
[object Object]Hai điểm cần chú ý: dùng broadcast() để nhiều listener cùng nghe được; và nhớ close() stream ở cuối test, nếu không test suite có thể bị treo ở lần chạy sau.
Mock HTTP: MockClient và adapter giả cho Dio
Khi test ở mức thấp hơn (kiểm tra lớp service gọi API), hai cách gọn nhất là:
[object Object][object Object]Điểm mạnh của MockClient: nó kiểm tra được cả interceptor, header mặc định và thời gian chờ — những thứ mà mock ở mức cao hơn sẽ bỏ qua. Nếu bạn từng gặp lỗi "header xác thực không được gắn" chỉ xuất hiện trên production, đây là loại test sẽ phát hiện sớm.
setUp, tearDown và rò rỉ giữa các test
Test chỉ đáng tin khi chạy độc lập: chạy riêng, chạy cả bộ, chạy theo thứ tự ngẫu nhiên đều cho cùng kết quả. Hai công cụ để bảo đảm điều đó:
[object Object]Hai lỗi thường gặp:
- Tạo mock ở cấp
main()ngoàisetUp→ các test chia sẻ cùng một mock, vàverifyở test sau có thể tính cả lời gọi của test trước. - Quên
tearDownchoStreamControllerhoặcTimer→ test suite chạy chậm dần hoặc bị treo.
Nếu bạn muốn kiểm tra nghiêm ngặt rằng không có lời gọi thừa, dùng verifyNoMoreInteractions(repository) ở cuối test. Hãy dùng tiết chế: nó hữu ích cho các lớp có hợp đồng rõ ràng, nhưng dễ làm test giòn khi bạn chỉ muốn thêm một lời gọi không quan trọng.
Mock thời gian, ID và số ngẫu nhiên
Ba nguồn không xác định này là nguyên nhân phổ biến nhất khiến test "lúc đỏ lúc xanh". Cách xử lý sạch nhất là không gọi trực tiếp DateTime.now(), Uuid().v4() hay Random() trong logic nghiệp vụ, mà truyền chúng vào.
[object Object][object Object]Cách này tốt hơn hẳn việc cố "giả lập" thời gian bằng thư viện: bạn kiểm tra được mọi mốc thời gian quan tâm (đúng ngày, cuối tháng, chuyển năm) mà không phụ thuộc vào lúc chạy test.
Khi việc mock phản tác dụng
Mock là công cụ, không phải mục tiêu. Ba dấu hiệu bạn đang mock quá nhiều:
- Test dài hơn code đang kiểm tra, toàn
whenvàverifymà gần như không có khẳng định về kết quả nghiệp vụ. - Test vẫn xanh dù logic sai. Xảy ra khi mock trả về dữ liệu mà thực tế không bao giờ trả — ví dụ trả
nullcho trường mà API luôn có. - Sửa code phải sửa hàng loạt test dù hành vi bên ngoài không đổi. Nghĩa là test đang kiểm tra cách triển khai, không kiểm tra hành vi.
Cách giảm: đặt khẳng định vào kết quả cuối (state, dữ liệu trả về) thay vì vào từng lời gọi trung gian; dùng lớp giả có trạng thái thay cho mock khi luồng có nhiều bước; và giữ test ở mức hành vi người dùng quan tâm.
Lỗi thường gặp với Mockito
MissingStubError— gọi một hàm chưa khai báowhen. Cần thêm stub, hoặc dùngMockcó cấu hình trả về mặc định.- Quên
asynctrongthenAnswerkhi hàm trảFuture. - Dùng
any()cho tham số bắt buộc không truyền — với mockito,any()chỉ dùng được trongwhen/verify, không truyền được vào hàm thật. verifysai số lần gọi vì mock được tạo ngoàisetUpvà dùng chung giữa các test.- Không sinh lại mã sau khi đổi interface → hành vi cũ vẫn còn.
- Mock cả lớp thuần logic, khiến test không kiểm tra được gì có ý nghĩa.
- Quên
close()stream trong test dữ liệu realtime.
Câu hỏi thường gặp
Có nên mock BuildContext không?
Không nên. Nếu bạn thấy mình cần mock BuildContext, thường là dấu hiệu logic đang bị đặt sai chỗ — hãy tách logic khỏi widget và test nó như một lớp Dart thuần. Với giao diện, dùng widget test và pumpWidget thay vì mock context.
Mockito có kiểm tra được getter và setter không?
Có, nhưng cần cẩn thận: getter được khai báo bằng when(mock.someProperty).thenReturn(value). Với mocktail, cách viết tương tự nhưng không cần sinh mã.
Làm sao kiểm tra thứ tự các lời gọi?
Dùng verifyInOrder([...]) với danh sách các lời gọi theo thứ tự mong đợi. Chỉ nên áp dụng khi thứ tự thật sự quan trọng (ví dụ phải lưu xuống máy trước khi gọi API); nếu không, test sẽ giòn vô ích.
Khi nào nên chạy test với dữ liệu thật thay vì mock?
Với logic chuyển đổi dữ liệu phức tạp (parse JSON lớn, xử lý ngày tháng, tính toán tiền tệ), hãy dùng dữ liệu thật lấy từ phản hồi API đã ghi lại. Mock không giúp phát hiện sai lệch giữa giả định của bạn và dữ liệu thật.
Ví dụ đầy đủ: test luồng thêm vào giỏ hàng
Ví dụ này ghép các kỹ thuật ở trên thành một test hoàn chỉnh, gần với công việc thật: kiểm tra dữ liệu gửi lên, kiểm tra trạng thái sau khi gọi, và kiểm tra nhánh lỗi.
[object Object]Bốn test này kiểm tra đúng bốn thứ khác nhau, và đó là điều đáng học từ ví dụ: một test cho luồng chính, một test cho trường hợp cộng dồn, một test cho dữ liệu đầu vào sai, và một test cho lỗi từ phụ thuộc. Rất nhiều dự án chỉ có test đầu tiên — chính vì vậy lỗi thường xuất hiện ở ba nhánh còn lại.
Hai kỹ thuật quan trọng cần chú ý trong ví dụ: captureAny để lấy lại đối tượng đã được truyền vào repository (nhờ đó kiểm tra được tổng tiền hoặc thời gian tạo), và verifyNever để khẳng định rằng không có thao tác ghi nào xảy ra khi dữ liệu đầu vào sai.
Bốn câu hỏi trước khi viết một mock
Trước khi thêm Mock vào file test, hãy tự hỏi bốn câu này. Chúng giúp bạn tránh việc mock tràn lan và giữ bộ test có giá trị:
1. Đối tượng này có phải ranh giới với bên ngoài không? Nếu nó là lớp logic thuần của bạn, đừng mock — hãy gọi thật và kiểm tra kết quả. Mock một lớp logic chỉ xác nhận rằng bạn gọi đúng hàm, không xác nhận rằng logic đúng.
2. Tôi đang kiểm tra hành vi hay cách triển khai? Khẳng định quan trọng nên nhắm vào kết quả: dữ liệu được lưu, trạng thái sau cùng, dữ liệu gửi lên API. Khẳng định vào từng lời gọi trung gian khiến test đỏ mỗi lần bạn refactor dù hành vi không đổi.
3. Có bản giả bằng tay nào ngắn hơn không? Với những phụ thuộc đơn giản, một lớp giả 10–15 dòng thường dễ đọc hơn vài when lồng nhau, đặc biệt khi bạn cần nó giữ trạng thái giữa các lời gọi.
4. Tôi có đang test cả nhánh lỗi chưa? Nếu chỉ có test cho luồng thành công, bạn chưa kiểm tra phần lớn rủi ro. Mỗi mock nên đi kèm ít nhất một test cho trường hợp nó ném lỗi hoặc trả về rỗng.
Một dấu hiệu cho thấy bạn đã mock vừa đủ: file test đọc lên vẫn thấy nghiệp vụ. Khi file test chỉ toàn when, verify và any() mà không có khẳng định nào về dữ liệu hay trạng thái, gần như chắc chắn test đang kiểm tra cách triển khai chứ không kiểm tra hành vi của sản phẩm.
Để đi sâu hơn vào việc test tầng gọi API với các trường hợp lỗi và phân trang, xem bài viết unit test cho service gọi API. Còn nếu bạn cần kiểm tra giao diện thay vì logic, hãy dùng widget test — xem bài về golden test cho UI.
Khi nào cần mock trong widget test
Test giao diện dùng được mock theo cách khác với test logic. Điều quan trọng là hiểu rõ giới hạn để không viết ra những test vừa chậm vừa không kiểm tra được gì.
[object Object]Ba điểm khác biệt so với unit test:
- Mock chỉ nên nằm ở ranh giới dữ liệu. Mock tầng repository hoặc service là hợp lý; mock
BuildContext,NavigatorhayMediaQuerythường là dấu hiệu logic đang bị đặt sai chỗ. Với điều hướng, hãy kiểm tra bằng cách tìm màn hình mới sau khipumpAndSettlethay vì khẳng định rằngNavigator.pushđã được gọi. - Thời gian là yếu tố khác biệt. Mock trong widget test phải trả về
Futuređã hoàn thành đểpumpAndSettlecó thể chờ. Nếu bạn mô phỏng độ trễ bằngFuture.delayedthật, test sẽ chậm và dễ lúc đỏ lúc xanh — hãy hoàn thành ngay và dùngtester.pump(duration)khi cần kiểm tra trạng thái trung gian. - Số lượng mock nên rất ít. Một widget test tốt thường chỉ cần một đến hai mock. Nếu bạn cần mock năm lớp để màn hình hiển thị được, đó là dấu hiệu màn hình đang phụ thuộc quá nhiều và nên tách bớt.
Với những trạng thái như "đang tải", hãy tạo mock trả về Completer chưa hoàn thành để giữ giao diện ở trạng thái đó, rồi mới hoàn thành nó và kiểm tra trạng thái kết quả:
[object Object]Kỹ thuật Completer này rất hữu ích vì nó cho phép kiểm tra những trạng thái trung gian — đúng những trạng thái mà người dùng thật sẽ nhìn thấy khi mạng chậm, nhưng lại gần như không bao giờ được kiểm tra nếu bạn chỉ dùng mock trả về kết quả ngay.
Kết luận
Mockito giúp:
- ✅ Test không phụ thuộc network
- ✅ Test nhanh hơn
- ✅ Test error cases dễ dàng
- ✅ Control test data
Tóm tắt:
- Setup: Thêm mockito và build_runner
- Generate: Tạo mock classes
- Mock: Setup mock behavior
- Verify: Verify calls được thực hiện
Sử dụng Mockito đúng cách sẽ giúp tests của bạn nhanh và reliable hơn! 🚀
Học Flutter bài bản theo lộ trình
Nếu bạn muốn học có người sửa bài thay vì tự ghép tài liệu, Flutter Hướng Nghiệp đào tạo theo 3 cấp:
- Flutter Cơ Bản — Dart, widget, giao diện, 28 buổi.
- Flutter Nâng Cao — state management, REST API, Firebase, 28 buổi.
- Flutter Chuyên Sâu — back-end, AI/ML, publish lên store, 53 buổi.
Lớp online trực tiếp qua Zoom/Google Meet, có bản ghi lại — không bắt buộc đến cơ sở.
Xem lịch khai giảng hoặc so sánh 5 cách học lập trình mobile nếu còn phân vân.
