In this lesson, you will learn how to inject artificial delays and failures into your network requests to ensure your application’s resiliency patterns, such as retries and circuit breakers, function correctly under duress.
In a distributed Node.js environment, the "happy path" is a luxury; network partitions, timeouts, and intermittent latency are inevitable. To test if your service handles these gracefully, you must stop relying on perfect network conditions during your integration tests. Instead, you need to simulate instability programmatically.
Injecting Latency and Failures
You can intercept outbound requests at the transport layer to induce artificial overhead or trigger intentional failures. Using a library like nock or a custom proxy, you can force a dependency to hang for five seconds or return a 503 Service Unavailable error before it succeeds.
Consider this example using nock to test if a retry mechanism correctly intercepts a transient failure:
const nock = require('nock');
test('should retry on intermittent failure', async () => {
// Simulate a failure followed by a success
const scope = nock('https://api.external.com')
.get('/data')
.reply(503) // First attempt fails
.get('/data')
.delayConnection(2000) // Second attempt has 2s latency
.reply(200, { success: true });
const result = await fetchWithRetry('https://api.external.com/data');
expect(result.success).toBe(true);
expect(scope.isDone()).toBe(true);
});
Why This Matters for Resiliency
If you are implementing a Circuit Breaker (like opossum), unit testing won't suffice; you need to prove that after a series of injected failures, the circuit transitions to an open state and prevents further requests.
By forcing artificial delays that exceed your client’s timeout threshold, you verify that your error boundaries correctly catch ETIMEDOUT errors rather than letting your event loop get clogged by unhandled pending promises. Testing these failure modes ensures that your application fails fast and recovers predictably, rather than cascading errors to the end-user.
Key Takeaway: Injecting network instability into your test suite transforms your resilience logic from theoretical code into a battle-tested safety net.