The Four Kinds of Stand-Ins
When Pariksha the inspector needed to test the gate keeper in isolation, she could not use the real treasury because the real treasury's behaviour was unpredictable in tests. She used stand-ins instead. There were four kinds. A stub responded with pre-set answers regardless of how it was called: "always say the treasury is full." A spy did the same but also recorded every question asked of it. A mock was a spy with rules: "if I am not called exactly twice, the test fails." A fake was a working treasury built cheaply for testing — an in-memory treasury that behaved correctly but stored nothing permanently. Each kind suited a different situation. Confusing them produced tests that passed for the wrong reasons.
“Stub: pre-set responses. Spy: records calls. Mock: spy with expectations. Fake: working lightweight replacement. Know which one the situation requires.”
The Spy Who Listened at the Gate
The gate keeper needed to notify the scribe every time a visitor arrived. Pariksha wanted to verify that the notification was sent — not that the scribe received it correctly (that was the scribe's own test). She replaced the real scribe with Gawah the spy: a stand-in who accepted notifications without doing anything with them, but who kept a perfect record of every notification received. After the gate keeper processed a visitor, Pariksha asked Gawah: "Were you called?" Gawah said yes. "How many times?" Once. "With what message?" She checked the record. The notification was correct. The gate keeper's responsibility had been verified without involving the real scribe.
“A spy verifies that a collaboration happened — that a method was called, with what arguments, how many times — without coupling to the collaborator's implementation.”
The Fake Treasury That Actually Worked
Some tests required a treasury that could store and retrieve items, not merely respond with pre-set answers. Stubs were inadequate: the gate keeper stored a key, then retrieved it later in the same test — the stub could not track state between calls. Kalpana the fake was built: a small, in-memory treasury that stored keys in a map and retrieved them on request. It was not the real treasury — it used no stone, no guards, no locks. But it behaved correctly for any test that needed to store and retrieve. The fake was written once and used in hundreds of tests. It was tested itself to verify it behaved like the real treasury. It was simpler, faster, and perfectly deterministic.
“A fake implements the real interface with a simpler mechanism. Use a fake when a stub cannot track state between interactions.”
The Over-Mocker's Betrayal
Mohit the over-mocker wrote tests that specified exactly how the gate keeper should call the treasury: "call the `retrieveKey` method, then call `logAccess`, then call `notifyGuard` — in that exact order, with those exact method names." The tests passed. A season later, the gate keeper was refactored: `retrieveKey` and `logAccess` were merged into a single `retrieveAndLog` method that did both more efficiently. The behaviour was unchanged — visitors still passed through, access was still logged. But every one of Mohit's tests broke, because `retrieveKey` and `logAccess` no longer existed as separate methods. Pariksha spent three days updating tests that tested internal structure rather than external behaviour. None of the changes had introduced a bug.
“Mock the interface, not the implementation. Tests that assert how code works internally break on every refactor. Tests that assert what code does externally survive refactoring.”
The Rule About Ownership
Pariksha established a strict rule: stand-ins were to be used only for collaborators that the test's author owned and controlled. The outer market gate — a system built by another guild entirely — was never to be stubbed. If Nagar's gate keeper needed to call the market gate, the test should use either the real market gate (containerised and controlled) or a contract-tested stand-in whose behaviour was verified against the real gate's contract. Stubbing the market gate with invented responses was dangerous: the invented responses might not match what the real gate actually returned, and the test would pass while the real integration failed. "Do not mock what you do not own," Pariksha said. "And when you must, verify the mock against the contract."
“Do not mock third-party systems with invented behaviour. Use contract-verified doubles or real instances. Invented responses create false confidence.”
The Stand-In That Forgot Its Role
After each test, Pariksha had a final rule: stand-ins must forget what they recorded and return to their original state. A spy that remembered yesterday's calls would contaminate today's test. If two tests shared the same spy and one test left a call recorded, the second test would find a call it never made and report a false failure — or, worse, a false pass. The rule was: every stand-in was created fresh for each test, or reset completely at the end of each test. No test inherited another test's mock state. Like the tent that rose and fell for each integration test, the stand-in held its role for one test only. After the test, it returned to blankness, ready to play a different role in a different story.
“Reset or recreate mocks between tests. Shared mock state creates invisible test-order dependencies that produce unreliable results.”
🪔 Deepak — the lamp of meaning · what this fable means in code
Test doubles are objects that stand in for real dependencies during testing. Terminology (from Gerard Meszaros): Stub — returns pre-configured values; Spy — records how it was called; Mock — a spy with built-in expectations (the test fails if expected calls do not happen); Fake — a working implementation with a simpler mechanism (in-memory database). In Vitest: `vi.fn()` creates a mock function that acts as a spy; `vi.fn().mockReturnValue(x)` makes it a stub; `vi.spyOn(obj, "method")` wraps a real method to record calls while still invoking the original. `vi.mock("module")` hoists the entire module replacement to the top of the file (due to Babel/Vite transform). `vi.restoreAllMocks()` in `afterEach` resets spy state. The "don't mock what you don't own" principle: third-party modules should be tested with real instances or contract-verified adapters — creating invented behaviour for an external API produces tests that pass but do not guarantee real integration. Prefer testing behaviour (what the output is) over testing implementation (which methods were called in which order).

