Mock vs Stub vs Fake vs Spy 完整指南 — Test Double 用對位置
Test Double 四種類型完整解釋。Mock / Stub / Fake / Spy 差別、用對位置、Playwright / pytest / Jest 實戰、第三方 API mock、WireMock / MSW / Pact 工具。
💡 本文原刊於 qa.9niche.com,2026-08 併入 9niche.com 懶人包,內容照原文完整搬遷。
目錄
1. 前言
「我們的 test 都用 mock」聽起來合理、實際上多數人把 Mock / Stub 混為一談、亂用一通。這篇用 Martin Fowler 的標準分類講清楚。
2. 四種 Test Double 一張圖
3. 對照表
| 類型 | 給回應 | 驗證呼叫 | 真實邏輯 | 何時用 |
|---|---|---|---|---|
| Dummy | ❌ | ❌ | ❌ | 不會被用、佔位 |
| Stub | ✓ 固定 | ❌ | ❌ | 取代外部、給可控資料 |
| Spy | ✓ 部分 | ✓ | ✓ | 保留行為、加觀察 |
| Mock | ✓ 可設 | ✓ 強制 | ❌ | 驗證互動 |
| Fake | ✓ 動態 | ❌ | ✓ 簡化 | 簡化的真實實作 |
4. 範例:登入服務測試
Stub — 給固定回應
// LoginService 依賴 UserRepository
class LoginService {
constructor(private repo: UserRepository) {}
async login(email: string, password: string) {
const user = await this.repo.findByEmail(email);
return user?.password === password;
}
}
// Stub:給固定回應、不在乎 findByEmail 被叫幾次
const userStub: UserRepository = {
findByEmail: async () => ({ email: '[email protected]', password: 'xxx' }),
};
const service = new LoginService(userStub);
const result = await service.login('[email protected]', 'xxx');
expect(result).toBe(true);
Mock — 驗證呼叫
const mockRepo = {
findByEmail: jest.fn().mockResolvedValue({ email: '[email protected]', password: 'xxx' }),
};
const service = new LoginService(mockRepo);
await service.login('[email protected]', 'xxx');
// 驗證 findByEmail 真的被呼叫、用對參數
expect(mockRepo.findByEmail).toHaveBeenCalledWith('[email protected]');
expect(mockRepo.findByEmail).toHaveBeenCalledTimes(1);
Spy — 真行為 + 觀察
const realRepo = new UserRepository(realDb);
const spy = jest.spyOn(realRepo, 'findByEmail');
const service = new LoginService(realRepo);
await service.login('[email protected]', 'xxx');
// 真的去查 DB、但也記錄了呼叫
expect(spy).toHaveBeenCalledWith('[email protected]');
Fake — 簡化真實
// In-memory 取代真 DB
class FakeUserRepository implements UserRepository {
private users = new Map<string, User>();
async findByEmail(email: string) {
return this.users.get(email);
}
async save(user: User) {
this.users.set(user.email, user);
}
}
const fake = new FakeUserRepository();
await fake.save({ email: '[email protected]', password: 'xxx' });
const service = new LoginService(fake);
const result = await service.login('[email protected]', 'xxx');
// 行為跟真 DB 一樣、但快且 isolated
5. 何時用哪個
6. QA 在 E2E / API 測試常用
Playwright route mock (Stub)
test('handle API error', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server Error' }),
});
});
await page.goto('/users');
await expect(page.getByText('系統忙碌')).toBeVisible();
});
pytest + responses (Stub)
import responses
@responses.activate
def test_payment_success():
responses.add(
responses.POST,
"https://api.stripe.com/v1/charges",
json={"id": "ch_123", "status": "succeeded"},
status=200,
)
result = pay(amount=100)
assert result["status"] == "succeeded"
WireMock — 第三方 API mock server
docker run -d -p 8080:8080 wiremock/wiremock
// mappings/stripe.json
{
"request": {
"method": "POST",
"url": "/v1/charges"
},
"response": {
"status": 200,
"body": "{\"id\":\"ch_123\",\"status\":\"succeeded\"}"
}
}
MSW (Mock Service Worker) — 最現代
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/users/:id', ({ params }) => {
return HttpResponse.json({ id: params.id, name: 'Alice' });
}),
];
MSW 強項:同一份 mock 給 Storybook / unit test / E2E 都用、超強 DX。
7. Mock 反模式
原則:Mock 邊界、不 Mock 自己
✓ Mock 邊界(HTTP API / DB / 第三方)
✓ Mock 不可控(時間、隨機、UUID)
✗ Mock 自己 module 的 internal function
✗ Mock 純函式
8. Contract Testing 補位
Mock 跟真實 API 不同步 → 用 Pact / Contract Testing 強制兩邊對齊。
9. 工具地圖
| 用途 | 工具 |
|---|---|
| HTTP request mock | nock (Node) / responses (Python) |
| Service Worker | MSW |
| 完整 API mock server | WireMock / Mockoon / Beeceptor |
| Test framework 內建 | jest.mock / pytest-mock |
| Contract testing | Pact / Spring Cloud Contract |
| DB Fake | SQLite in-memory / TinyDB |
10. 面試常考
Q: Mock 跟 Stub 差別?
好答案(30 秒):
Stub 給固定回應、不驗證互動 — 我設定它回什麼、test 就用。Mock 是 Stub 超集、額外驗證 collaborator 被怎麼呼叫 — 我除了設回應、還會 expect 它被呼叫過 N 次、用對參數。簡單講 Stub 是行為驗證、Mock 是互動驗證。
Q: 你會在哪些情況不用 mock?
好答案:
純函式不用、自己 module 內部的 helper 不用。Integration test 階段我會用真實 DB(in-memory Fake)。E2E 我會 mock 第三方 API(Stripe sandbox 或 stub)但保留前後端真實串接。
11. 給 QA 的 5 句
- 預設用 Stub、需要驗證互動才升 Mock
- Mock 邊界、不 Mock 自己 code
- DB / 第三方 → Fake 或 contract test
- MSW 是 2026 後 mock 首選
- 過度 mock 的 test 全綠但 prod 全爆
12. 最後
Mock / Stub / Fake / Spy 是 QA 跟 dev 共通語言。用對名詞、用對位置 → 一週寫 case 順 5 倍。從每天問自己「我這真的需要 Mock 還只是 Stub?」開始、半年後你會跟 dev 對話完全不同 level。
延伸:
相關連結
微服務架構下 spec review 要看的東西。Consumer-Driven Contract Testing、Pact 工作流、跨服務 schema 漂移、版本兼容、事件驅動契約。
自動化測試最大坑:test data 管理。Fixture vs Factory vs Mother、Seed 策略、cleanup 模式、跨環境資料、敏感資料處理。附 pytest / Playwright 範例。
從零搭起 API 測試框架。pytest fixtures、requests session、JSON schema 驗證、auth 處理、CI 整合,附完整範例。
相關懶人包
2026 QA 趨勢實戰:我看到的 5 個轉變(AI、Shift-Left、Observability)
從手動 QA 到 AI 輔助、從測試金字塔到測試獎盃。這篇分享我這 10+ 年看 QA 從「測完才知道」到「shift-left + AI」的真實觀察。
AI / LLM 功能 Spec Review — 幻覺 / 評估 / 成本 / 法遵 8 個必問
AI 功能 spec review 完整指南。LLM 不確定性處理、評估指標、Prompt versioning、成本控制、安全護欄、法遵(EU AI Act / GDPR)、Fallback、人工 review 流程。
AI Agent 系統測試 — 自主執行 / 工具呼叫 / 多步推理的 QA 策略
測試 AI Agent 完整方法。Tool calling 驗證、Trajectory 評估、Failure mode 分類、無限迴圈防止、成本上限、安全 sandbox、Multi-agent 協作測試。
一般聲明
本站提供之資訊僅供參考,不保證其完整性與正確性。使用者應自行判斷資訊之適用性。