test double
A controlled substitute makes a dependency predictable in a test.
Use this when You need to exercise a timeout or rejection that is hard to trigger reliably.
without the term
“I need to test what happens when the payment API times out, but I can't make the real one time out on demand. Can you fake it just for the test?”
with the term · How do you ask a model for test double?
Use a test double at the external service boundary to simulate a timeout. Assert recovery, and keep a separate test of the real integration.
What is test double?
Test double is the umbrella term for substitutes such as stubs, fakes, and mocks, which serve different testing purposes.
Why does test double matter when vibe coding?
An agent can over-mock a test until it only confirms its own assumptions.
How do you do test double?
Substitute the smallest boundary needed for the scenario. Keep separate coverage of the real integration.
What goes wrong with test double?
The substitute returns a response the real service would never produce.
Before and after
A product screen loads a price from an external service. You need to check its timeout state without waiting for the real service to become unavailable. Keep the screen logic real and substitute only the price-loading boundary.
Before
The happy path returns a price, but a rejected dependency escapes without a defined screen state. A happy-path check alone leaves that missing behavior undiscovered.
async function before(loadPrice) {
return { status: 'ready', price: await loadPrice() };
}After
Use a controlled loader that rejects with TimeoutError to expose the failure. Handle that specified error as an unavailable state, preserve the success path, and let unexpected errors propagate instead of hiding them.
async function after(loadPrice) {
try {
return { status: 'ready', price: await loadPrice() };
} catch (error) {
if (error.name !== 'TimeoutError') throw error;
return { status: 'unavailable', message: 'Price unavailable. Try again.' };
}
}Check the change
The controlled timeout makes the initial handler reject. The revised handler returns the specified unavailable state, still returns a real price from the success stub, and propagates an unexpected failure. The test double replaces the loader, not the handler being checked.
Save the runnable comparison, then run node --test test-double.mjs with Node.js 22.12 or later.
Download the complete teaching example: the bug, prompt, runnable code, expected result, limits, and sources, linked back to this page.
Show the assertions
const timeout = async () => {
throw new DOMException('Price service timed out', 'TimeoutError');
};
test('a controlled timeout exposes missing recovery', async () => {
assert.deepEqual(await before(async () => 100), { status: 'ready', price: 100 });
await assert.rejects(before(timeout), { name: 'TimeoutError' });
});
test('the real handler recovers and still handles success and unexpected errors', async () => {
let calls = 0;
const unavailable = await after(async () => { calls++; return timeout(); });
assert.deepEqual(unavailable,
{ status: 'unavailable', message: 'Price unavailable. Try again.' });
assert.equal(calls, 1);
assert.deepEqual(await after(async () => 100), { status: 'ready', price: 100 });
await assert.rejects(after(async () => { throw new Error('invalid response'); }),
/invalid response/);
});When this is not enough
A test double is a substitute used in a test; it does not make the application resilient by itself. This stub assumes the boundary uses TimeoutError. Match the real client contract and keep separate integration checks for request shape, decoding, authentication, and actual error types. A stub that returns the desired final screen state without running the handler would not test recovery.
The next decision
- test isolation: Keep doubles and recorded calls independent between tests.
- test-driven development: Write the missing recovery expectation before implementing it.
Sources and verification
- Martin Fowler: Test Double
Test double as the umbrella term and stubs as controlled canned responses.
- MDN: AbortSignal timeout
TimeoutError is used by AbortSignal.timeout; the example assumes that explicit boundary contract.
Sources checked . These are illustrative examples with automated checks. How we check content.
Did this help with your task?
After trying the prompt or check, tell us what happened. This optional response records only your choice and this term.