Làm sao để biết khi Team Catalog đổi props của ProductCard thì trang chủ không bị lỗi "trắng màn hình"?
1. Phân tích thiết kế: Chiến lược Test phân tầng
Cách làm truyền thống: E2E Build-All
Mọi thay đổi nhỏ đều yêu cầu chạy lại toàn bộ bộ test E2E (Playwright/Cypress) trên một môi trường Monolith lớn.
Nhược điểm:
- Tốc độ chậm khủng khiếp: Chạy E2E cho một siêu ứng dụng có thể mất vài tiếng. Team không thể deploy nhanh được.
- Flaky Tests: Test hay bị fail "ngẫu nhiên" do hạ tầng mạng hoặc các team khác đang update API không liên quan.
- Khó cô lập lỗi: Khi E2E fail, rất khó biết lỗi nằm ở code của Host hay do Remote vừa mới update.
Cách làm mới: Contract Testing & Mocking
Sử dụng các "Bản giao kèo" (Contracts) để đảm bảo Host và Remote luôn hiểu nhau mà không cần chạy cả hệ thống lên để test.
Ưu điểm & Vấn đề giải quyết:
- Tính tự tin (Confidence): Team Catalog biết chắc chắn rằng component của mình không làm hỏng Host App.
- Fail Fast: Phát hiện lỗi ngay từ lúc viết code hoặc lúc chạy CI của từng repo riêng lẻ.
- Mocking: Sử dụng MSW (Mock Service Worker) để giả lập API ngay tại ranh giới của MFE.
2. Contract Testing (Pact.io)
Đây là trái tim của việc Testing trong MFE.
- Consumer (Host): "Tôi cần một component có prop
pricekiểu số". - Provider (Remote): "Tôi cam kết luôn cung cấp prop
pricekiểu số". - Nếu Provider đổi
pricethành kiểu chuỗi (string), bộ test Contract sẽ fail ngay lập tức dù chưa cần chạy app.
3. Đánh đổi (Trade-offs)
- Chi phí thiết lập: Cần thời gian để viết và duy trì các bản Contract giữa các team.
- Over-testing: Dễ dẫn đến việc viết quá nhiều test trùng lặp giữa Unit test và Integration test.
4. Code minh họa: Mocking với MSW (Cross-MFE)
tsx:
// catalog-mfe/src/mocks/handlers.ts
export const handlers = [
rest.get('/api/products', (req, res, ctx) => {
return res(ctx.json([{ id: 1, name: 'Shopee Phone' }]))
}),
]
// app-shell/src/test-setup.ts
// Gộp tất cả mock của các team lại để chạy E2E
import { handlers as catalogHandlers } from 'catalog/mocks';
import { handlers as authHandlers } from 'auth/mocks';
const server = setupServer(...catalogHandlers, ...authHandlers);Bài tập: Pipeline CI/CD
Hãy thiết kế quy trình CI cho một PR của Team Catalog:
- Linter & Unit Test: Kiểm tra logic nội bộ.
- Contract Validation: Kiểm tra xem các component expose có vi phạm "hợp đồng" với Host App không.
- Ghost Deployment: Deploy bản Preview và chạy bộ test Integration (Playwright) chỉ cho phần Product list.
- Nâng cao: Làm sao để Host App tự động thông báo lỗi cho Team Catalog nếu một bản update làm sụt giảm chỉ số Performance (LCP) quá 10%?
Câu hỏi phỏng vấn
Q: Contract Testing (Pact) khác gì so với Integration Testing truyền thống?
- Trả lời: Integration testing kiểm tra xem hai hệ thống có chạy đúng với nhau "ngay bây giờ" không. Còn Contract Testing kiểm tra xem chúng có "hứa" sẽ chạy đúng với nhau "mãi mãi" không.
- Team Catalog viết một Contract: "Tôi hứa cung cấp Button với prop
color: string". - Nếu Team Catalog đổi
colorthành object mà không báo trước, Contract Test sẽ báo lỗi ngay trong lúc Build, trước khi code kịp đẩy lên Production.
- Team Catalog viết một Contract: "Tôi hứa cung cấp Button với prop
Q: Làm sao để chạy E2E Testing cho Microfrontends mà không tốn quá nhiều thời gian và tiền bạc?
- Trả lời: Đừng chạy toàn bộ E2E cho mọi PR (Pull Request).
- Ở PR: Chỉ chạy Unit Test và Contract Test của riêng MFE đó.
- Ở Staging: Chạy Integration test giữa Host và Remote đó.
- Nightly (Hàng đêm): Chạy toàn bộ bộ E2E suite để đảm bảo luồng nghiệp vụ chính (Happy path) vẫn ổn định.
Q: Bạn xử lý việc Mock API như thế nào khi các MFE phụ thuộc lẫn nhau?
- Trả lời: Sử dụng MSW (Mock Service Worker). Mỗi MFE mang theo bộ MSW handlers của riêng nó. Shell App có thể gộp các handlers này lại để tạo ra một Fake Backend duy nhất cho môi trường phát triển và môi trường test. Điều này giúp các team có thể làm việc độc lập ngay cả khi API thật chưa xong.