Test Error Branches with Injected Failures
Inject dependency failures at call boundaries to exercise catch and fallback paths that normal runs never reach.
Task
Force dependency failures with injection so error handling code is covered and verified.
When to use this
Coverage shows catch blocks untested, so a fallback path is shipped without ever being exercised.
Prerequisites
- A module with injectable dependencies
- A test runner
- Knowledge of the failure modes
Steps
1. Inject a failing dependency and assert the fallback runs.
const gateway = { charge: async () => { throw new Error('timeout'); } };
const order = new OrderService(gateway, memoryStore);
await order.checkout(input);
expect(memoryStore.pending()).toHaveLength(1);Expected output: The catch branch persists a pending order as designed when the gateway fails.
2. Assert the error is surfaced with the right shape.
await expect(code).rejects.toMatchObject({ code: 'PAYMENT_UNAVAILABLE' });Expected output: Clients receive a stable, documented error code on failure.
Troubleshooting
Problem
Failure not injected where expected
Solution
Inject at the boundary the code actually calls; stubbing the wrong layer changes nothing.
Problem
Fallback hides the error entirely
Solution
Assert both the fallback effect and the visible error signal so silent failures do not pass.
Limitations
- Injection can diverge from real failure modes.
- Not a substitute for chaos testing.
- Requires the code to be injectable.
What this recipe does
Force dependency failures with injection so error handling code is covered and verified.
Coverage shows catch blocks untested, so a fallback path is shipped without ever being exercised.
Prerequisites
- A module with injectable dependencies
- A test runner
- Knowledge of the failure modes
Procedure
- Inject a failing dependency and assert the fallback runs.
const gateway = { charge: async () => { throw new Error('timeout'); } };
const order = new OrderService(gateway, memoryStore);
await order.checkout(input);
expect(memoryStore.pending()).toHaveLength(1);
Expected result: The catch branch persists a pending order as designed when the gateway fails.
- Assert the error is surfaced with the right shape.
await expect(code).rejects.toMatchObject({ code: 'PAYMENT_UNAVAILABLE' });
Expected result: Clients receive a stable, documented error code on failure.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows error branches show as covered and behave as designed, including the fallback side effects and error codes.
Troubleshooting
Failure not injected where expected — Inject at the boundary the code actually calls; stubbing the wrong layer changes nothing.
Fallback hides the error entirely — Assert both the fallback effect and the visible error signal so silent failures do not pass.
Limitations
- Injection can diverge from real failure modes.
- Not a substitute for chaos testing.
- Requires the code to be injectable.