← Recipes

阅读中文版本

Test Error Branches with Injected Failures

Inject dependency failures at call boundaries to exercise catch and fallback paths that normal runs never reach.

~30 mintestingreliabilityerror-handling

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

  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 result: The catch branch persists a pending order as designed when the gateway fails.

  1. 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.