it('charges once for concurrent retries with the same key', async () => {
const send = () => request(server)
.post('/payments')
.set('Idempotency-Key', 'key-123')
.send({ orderId, amount: 500 });
const [a, b] = await Promise.all([send(), send()]);
expect([a.status, b.status]).toContain(201);
expect(await countPayments(orderId)).toBe(1);
expect(gateway.charge).toHaveBeenCalledTimes(1);
});
Send two identical requests at the same time against a real database, then check that only one payment was made.
Why a real database? The protection usually depends on a unique constraint or a row lock on the key. A mocked repository can't reproduce a race: two requests running at the same moment.
Steps:
- Send both requests at once with
Promise.all. - Check there is exactly one payment row.
- Check the stubbed payment gateway was charged once.
- Check the responses match your design. Either both return the stored result, or one gets 409 while the first is still in progress.
Add sequential tests too:
- A retry after success returns the same body.
- The same key with a different payload is rejected.
One passing run doesn't prove there is no race, because races depend on timing. Repeat the concurrent test in a loop. Or add a hook that pauses the first request inside its transaction, so the second request always overlaps it.
- Use a real database so constraints and locks are really tested.
- Send concurrent requests with Promise.all.
- Check for one row and one charge, plus the responses your design expects.
- Also test a retry after success, and the same key with a different payload.
- Force the overlap or repeat the test, because races depend on timing.
Read more