Load this reference when: writing or changing tests, adding mocks, or adding cleanup/helper methods for tests.
在這些時候載入此參考: 撰寫或變更測試、加入 mock,或為測試加入清理/輔助方法。
A test exists to catch a specific break. Two principles govern everything here:
一支測試的存在是為了抓出某個特定的破壞。這裡的一切由兩條原則主宰:
1. Every test names the break it catches
2. Every test exercises the real thing
1. 每支測試都說出它抓的破壞
2. 每支測試都操練真實的東西
Strict TDD produces both naturally: a test written first and watched failing against real code has already proven it can fail, and only earns a mock when the real dependency proves slow or external.
嚴格的 TDD 自然而然兩者兼得:先寫、再對著真實程式碼看著它失敗的測試,已經證明它會失敗;而且只有當真實相依物件證明很慢或屬於外部時,它才值得用 mock。
Before writing the test body, answer: what production change should make this test fail — and is that change a bug or a decision? A test earns its place by catching a wrong branch, missing side effect, wrong argument, boundary case, or broken contract.
在寫測試主體之前,回答:什麼正式程式碼變更應該讓這支測試失敗——而那項變更是 bug 還是決策? 一支測試靠抓出不對的分支、缺失的副作用、錯誤的引數、邊界案例或壞掉的契約來贏得其位置。
Derive expectations independently. Use literals and hand-checked
fixtures; table-driven tests with literal want values are the preferred
shape. An expectation computed by the code under test — or its helpers —
passes no matter what that code does:
獨立推導期望值。 使用字面值與手工核對的 fixture;帶有字面 want 值的表驅動測試是偏好的形狀。由被測程式碼——或其輔助函式——算出的期望值,無論該程式碼做什麼都會通過:
// ❌ Mirror assertion: the same builder computes both sides — always true
const expected = buildSearchQuery({ tag: 'urgent' });
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected);
// ✅ Hand-derived literal
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"');
// ❌ 鏡像斷言:同一個 builder 算出了兩邊——永遠成立
const expected = buildSearchQuery({ tag: 'urgent' });
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected);
// ✅ 手工推導的字面值
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"');
No change detectors. If only intentional decisions can fail a test —
a constant's value, exact message wording, private structure — it fires
on redesign and sleeps through bugs. Test the behavior that depends on
the decision: not expect(MAX_RETRIES).toBe(5) but "a failing call is
retried 5 times and the 6th attempt never happens."
不要變更偵測器。 如果只有刻意的決策會讓測試失敗——常數的值、訊息的確切措辭、私有結構——它在重新設計時觸發,卻在 bug 出現時呼呼大睡。測試依賴該決策的行為:不是 expect(MAX_RETRIES).toBe(5),而是「失敗的呼叫會被重試 5 次,且第 6 次嘗試永不發生」。
Behavior, not text. Asserting that a script, skill, or config contains an exact line proves only that the source is the source. Run scripts against controlled inputs and assert outputs, side effects, or exit codes. Documents that instruct agents are tested by the consuming agent's behavior (superpowers:writing-skills); prose for humans earns no test at all.
行為,不是文字。 斷言一支 script、技能或設定檔包含某確切的一行,只證明了原始碼是原始碼。對著受控輸入執行 scripts,並斷言輸出、副作用或退出碼。指示代理的文件由使用它的代理的行為來測試(superpowers:writing-skills);給人讀的散文根本不需要測試。
Your code, not the framework. Test the contract your code makes at its boundaries — the route you register, the query you emit, the payload you produce. Upstream mechanics are their maintainers' tests to write (the classic: asserting your router invokes a registered handler — that is the framework's test, not yours). When upstream behavior genuinely surprised you, write one narrow characterization test naming the assumption. The same boundary applies inside your code: constructors, getters, constants, and trivial forwarding earn tests only when they validate, normalize, default, derive, enforce, or cause side effects — otherwise assert the first consumer-visible result that depends on them.
你的程式碼,不是框架。 測試你的程式碼在邊界上做出的契約——你註冊的路由、你發出的查詢、你產生的 payload。上游機制是其維護者該寫的測試(經典案例:斷言你的路由器呼叫了一個已註冊的 handler——那是框架的測試,不是你的)。當上游行為真的出乎你意料,寫一支命名該假設的窄特性化測試。同樣的邊界在程式碼內部也適用:建構子、getter、常數與瑣碎的轉發,只在它們驗證、正規化、預設、推導、強制或引發副作用時才值得測試——否則就斷言依賴它們的第一個消費者可見結果。
BEFORE writing the test body:
Name the production change that would make this test fail.
Cannot name one → redesign around an observable behavior
"The source text changed" → run the artifact and assert its effects
Only intentional decisions → change detector; test the behavior
that depends on the decision
Confirm the expected value is derived without the code under test.
IF it reuses the code's logic or helpers:
Replace it with a literal or hand-checked fixture
在寫測試主體之前:
說出會讓這支測試失敗的正式程式碼變更。
說不出任何一個 → 繞著一個可觀察行為重新設計
「原始碼文字變了」 → 執行產物並斷言其效果
只有刻意的決策 → 變更偵測器;測試依賴該決策的行為
確認期望值不是用被測程式碼推導的。
如果它重用了該程式碼的邏輯或輔助函式:
換成字面值或手工核對的 fixture
The mock earns no assertions. A mock assertion passes when the mock is present and fails when it is absent — it says nothing about the component. Assert the real component's behavior; if the mock is what you are checking, unmock it or delete the assertion.
mock 本身不值得任何斷言。 對 mock 的斷言在 mock 存在時通過、缺席時失敗——它對元件本身什麼都沒說。斷言真實元件的行為;如果你檢查的就是 mock,就解除 mock 或刪掉斷言。
// ✅ Real behavior
expect(screen.getByRole('navigation')).toBeInTheDocument();
// ❌ Mock existence
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
// ✅ 真實行為
expect(screen.getByRole('navigation')).toBeInTheDocument();
// ❌ mock 的存在
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
your human partner's correction: "Are we testing the behavior of a mock?"
你真人夥伴的指正:「我們是在測一個 mock 的行為嗎?」
Mock at the right level. Learn every side effect of the real method before replacing it; mock the slow or external operation and keep what the test depends on real. When unsure, run the test against the real implementation first and observe what actually needs to happen.
在正確的層級 mock。 在替換真實方法之前,先了解它的每個副作用;mock 慢的或外部的操作,並讓測試依賴的保持真實。不確定時,先對真實實作跑測試,觀察實際需要發生什麼。
// ❌ The mock swallows the config write that duplicate detection reads
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
}));
// ✅ Mock only the slow server startup; the config write stays real
vi.mock('MCPServerManager');
// ❌ mock 吞掉了重複偵測要讀的設定寫入
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
}));
// ✅ 只 mock 慢的伺服器啟動;設定寫入保持真實
vi.mock('MCPServerManager');
Make doubles specific. When arguments, call counts, or ordering are part of the contract, assert them — a fake that accepts anything verifies nothing. Give each branch (success, error, malformed) its own fixture or spy, so the wrong branch cannot satisfy the expectation.
讓替身具體。 當引數、呼叫次數或次序是契約的一部分時,斷言它們——一個什麼都接受的 fake 驗證不了任何事。為每個分支(成功、錯誤、格式錯誤)準備各自的 fixture 或 spy,這樣錯誤的分支無法滿足期望。
Mirror real data completely. Mock the complete structure as it exists in reality — all documented fields — not just the ones your test reads. Partial mocks fail silently when downstream code reads an omitted field: the test passes while integration breaks.
完整鏡像真實資料。 依照真實存在的完整結構 mock——所有文件化的欄位——而不只是你的測試讀的那幾個。部分 mock 在下游程式碼讀到被省略的欄位時會悄悄失敗:測試通過,整合卻壞了。
Production classes carry production methods only. Cleanup that only
tests need lives in test utilities, never as a destroy() on the
production class. Ask: is this method called only from tests? Does this
class own this resource's lifecycle? Wrong answers → test utility.
正式類別只放正式方法。 只有測試需要的清理邏輯住在測試工具裡,絕不作為正式類別上的 destroy()。問:這個方法只有測試會呼叫嗎?這個類別擁有這個資源的生命週期嗎?答案不對 → 放測試工具。
Prefer real components over complex mocks. When mock setup outgrows the test logic, mocks miss methods the real components have, or tests break when the mock changes, switch to an integration test with real components. your human partner's question: "Do we need to be using a mock here?"
偏好真實元件勝過複雜 mock。 當 mock 設定超出測試邏輯、mock 缺少真實元件有的方法、或 mock 一變測試就壞時,改用真實元件的整合測試。你真人夥伴的問題:「我們需要用 mock 嗎?」
BEFORE adding a mock or test helper:
List the real method's side effects; keep the ones the test
depends on real — mock the slow/external level below them.
Mock responses mirror the complete real structure.
A method only tests call lives in test utilities, not production.
About to assert on the mock itself?
Unmock it or delete the assertion.
在加入 mock 或測試輔助之前:
列出真實方法的副作用;保留測試依賴的為真實——
mock 它們之下慢的/外部的層級。
mock 回應要鏡像完整的真實結構。
只有測試會呼叫的方法要放在測試工具,不是正式程式碼。
正要對 mock 本身下斷言?
解除 mock 或刪掉斷言。
The TDD cycle — failing test, minimal implementation, refactor — is what "complete" means. Ship the tests the behavior needs and only those: trivial code and human prose earn none, and a test written to satisfy process costs maintenance forever.
TDD 循環——失敗的測試、最小實作、重構——就是「完成」的意義。交付行為需要的測試,而且只要那些:瑣碎的程式碼與給人讀的散文不值得任何測試,而為了應付流程寫的測試會讓維護成本永遠持續。
Before finishing, mentally mutate the production code; at least one test should fail for each realistic mutation:
在結束之前,在腦中突變正式程式碼;對每個合理的突變,至少應有一支測試失敗:
A mutation nothing catches marks the behavior as unprotected — or the test as tautological.
沒有任何測試抓得到的突變,標記該行為未受保護——或該測試是同義反覆。
| When you... | Do |
|---|---|
| Write any test | Name the break it catches — a bug, not a decision |
| Build an expected value | Derive it by hand; never with the code under test |
| Test a script or document | Run it / pressure-test its consumer; never grep its text |
| Reach for a dependency test | Test your boundary contract, not their documented mechanics |
| Want to assert on a mocked element | Test the real component, or unmock it |
| Are about to mock a method | Learn its side effects; mock the slow/external level |
| Build a mock response | Mirror the real structure completely |
| Need cleanup only tests use | Put it in test utilities |
| Watch mock setup balloon | Switch to an integration test with real components |
| Finish a test file | Run the mutation check |
| 當你... | 這樣做 |
|---|---|
| 寫任何測試 | 說出它抓的破壞——是 bug,不是決策 |
| 建一個期望值 | 手工推導;絕不用被測程式碼 |
| 測試 script 或文件 | 執行它/壓力測試它的消費者;絕不 grep 它的文字 |
| 想要依賴性測試 | 測你的邊界契約,不是他們文件化的機制 |
| 想在 mock 的元素上斷言 | 測真實元件,或解除 mock |
| 正要 mock 一個方法 | 了解它的副作用;mock 慢的/外部的層級 |
| 建一個 mock 回應 | 完整鏡像真實結構 |
| 需要只有測試用的清理 | 放進測試工具 |
| 看到 mock 設定失控 | 改用真實元件的整合測試 |
| 完成一個測試檔 | 跑突變檢查 |
*-mock test ID, or fails if you remove the mock*-mock 測試 ID,或移除 mock 就會失敗