JAVASCRIPT V2 · ENGINEERING · 120
Async Test / Fake / Mock:控制 Boundary,不是把整個世界假掉
當 code 依賴 network、repository、clock、timer 或 cancellation,unit test 若直接碰真實外部世界就容易慢、flaky、難重現。Test double 的目的,是把不穩定 dependency 換成可控制 contract,讓成功、失敗、延遲與取消都能重播。
Learning outcomes
- 正確等待 async test 完成。
- 區分 fake、stub-like value 與 interaction mock 的用途。
- 設計 dependency injection 讓 async boundary 可替換。
- 測 rejection、timeout-like behavior、cancellation 與 call arguments。
- 辨識 over-mocking 與「tests green, production broken」風險。
1. Why now:Async Failure 很難靠手動碰巧重現
network success
network 500
network reject
slow response
cancelled request
如果每一種情境都要等真實 server 配合,你的 test suite 不會穩定。工程上要能主動製造 dependency behavior。
2. Mental model:Production Logic 與 Dependency 分開
loadDashboard(repo)
↓
business logic
repo.load()
↓
production repo
or
fake repo in test
同一 service logic,換不同受控 dependency,就能重現 success/error/empty 等 paths。
3. Worked Example A:Async Test 必須真的 Await
test(
"loads students",
async () => {
const students =
await loadStudents(
fakeRepo
);
expectEqual(
students.length,
2
);
}
);
Test runner 必須知道 Promise 尚未 settled;否則 assertion/error 可能在 test 已被標成完成後才發生。
4. Fake Repository:可運作的簡化 Implementation
const fakeRepo = {
async load() {
return [
{
id: 1,
name: "Amy"
}
];
}
};
Fake 不需要模擬真 DB 每個細節,只要實作同一 dependency contract,並保持 deterministic。
5. Worked Example B:故意 Reject
const failingRepo = {
async load() {
throw new Error(
"DB unavailable"
);
}
};
await expectRejects(
() => loadStudents(
failingRepo
)
);
Failure path 只有在 test 能主動製造 failure 時才會真正被驗證。
6. Interaction Mock:只有 Interaction 本身是 Contract 才測
const save =
mockFn();
await submitStudent(
{ name: "Amy" },
{ save }
);
expectEqual(
save.calls.length,
1
);
expectEqual(
save.calls[0][0].name,
"Amy"
);
如果 contract 是「valid student 必須交給 repository.save」,interaction assertion 很合理。
7. Dependency Injection 不必先有 Framework
async function loadStudents(
repo
) {
const rows =
await repo.load();
return rows.map(
normalizeStudent
);
}
把 dependency 當參數就是最直接的 injection。重點是 ownership 與 contract 清楚,不是一定要 DI container。
8. Worked Example C:Cancellation Path
async function search(
query,
request
) {
return request(query);
}
const cancelled =
async () => {
const error =
new Error("cancelled");
error.name =
"AbortError";
throw error;
};
你可以用 fake request 精確重現 cancellation,而不必真的開 network 再 race abort timing。
9. Clock / Timer Boundary
function isExpired(
expiresAt,
now
) {
return now >= expiresAt;
}
把現在時間作為 dependency/value 傳入,test 就不需要真的等待或依賴測試機當下時鐘。
10. Common mistakes
A. 忘記 await / return Promise
Test 可能假綠。
B. 所有內部 function 都 mock
最後只證明 mocks 按你的劇本互相配合。
C. Fake 與 production contract 漂移
Integration tests 仍然必要。
D. 只測 success,不測 rejection
真正 incident 往往發生在 error path。
E. Mock call count 取代 behavior assertion
只有 interaction 是需求時才值得。
11. Debug evidence:Mock 全綠,Production 還是壞
Unit:
service + fake repo
PASS
Production:
service + HTTP + auth + DB
FAIL
這不代表 unit test 沒價值;它表示 failure 在未被 unit test 覆蓋的 integration boundary。121 之後的 integration testing 就是補這個層級。
12. Guided exercise:Success / Empty / Error Fake
const successRepo = { ... };
const emptyRepo = { ... };
const errorRepo = { ... };
同一個 loadStudents(repo) 跑三個 dependencies,驗證三種 contract。
13. Independent exercise:Mock Save Contract
寫 submitStudent(input, repo):valid 才呼叫 repo.save;invalid 要 throw 且 save.calls.length 維持 0。
const save =
mockFn();
const repo = { save };14. Challenge:Latest-Wins Async Test
為 132 的 latest-wins helper 建兩個 controllable promises,故意讓第二個先完成、第一個晚完成,證明舊結果回 "stale" 而不覆蓋最新值。
first starts
second starts
second resolves
first resolves laterProject checkpoint:Final Project Async Test Matrix
load/save
├─ success
├─ empty
├─ rejection
├─ cancellation
└─ stale completion
same app logic
controlled boundaries
Final Project 不需要 mock DOM 每個細節;pure rules 用 unit tests,async boundaries 用 fakes/mocks,整體 UI 再用 workshop acceptance checks。
15. Vocabulary / Summary
- test double:測試時替代 production dependency 的總稱。
- fake:可運作但簡化的 implementation。
- mock:常用來記錄/驗證 interaction。
- dependency injection:由外部提供 dependency,而非 function 內硬建立。
- over-mocking:mock 太多內部細節,導致 tests 與 production reality 脫節。
- async test:test completion 本身由 Promise settlement 決定。
16. Further reading
Knowledge check · Mastery
- 解釋 fake、mock 與 production dependency 的差異。
- 完成 success/error/cancelled async tests。
- 判斷哪些 interaction 值得 assert、哪些是 implementation detail。
- 完成頁面下方 Real Test Runner async/mock missions。