Back to News & Insights
Web Development August 19, 2026 · 5 min read

Your idempotency test probably cannot fail

My test for "do not announce the same payment twice" passed. Production sent two identical messages 142 milliseconds apart. The test was not weak — it was structurally incapable of catching the bug, and it looked like proof of the opposite.

Your idempotency test probably cannot fail

I had a test called "stays quiet when the plan is already what the event grants". It passed on every run for two weeks.

Then two identical "payment received" messages arrived on my phone, 142 milliseconds apart.

The test was not weak. It could not have failed. And that is a more interesting problem than a missing test, because a missing test at least looks like a gap — this one looked like proof.

Paddle sends subscription.created and subscription.activated for a single purchase, and it retries anything that does not answer 200. So two deliveries for one payment is not an edge case, it is Tuesday.

Read the current plan, compare, write, announce only if it changed. It reads correctly. It is correct, for one caller.

Two deliveries arrive. Both read free. Both see alreadyOnPlan === false. Both write basic. Both announce.

The window between the read and the write is where the whole bug lives, and it is exactly as wide as one round trip to the database.

Read it again with the bug in mind. It calls the handler once, against a row that is already in the target state. It asks: if I run this after the plan is already set, does it stay quiet?

A race requires two things to overlap. await post(...) runs one handler to completion before the next line executes, so the two deliveries never coexist. I could have added a second call, a third, a hundred — sequentially they would all pass, forever, while production kept sending doubles.

This is the part worth taking away. My test was not a bad test of concurrency. It was a test of something else that I had filed under concurrency, and the passing green tick was doing active harm: it told me the case was covered.

The fix in the product code is small, and I will get to it. But the test had to fail before the fix, or I would have no evidence the fix did anything.

Two handlers have to overlap. In JavaScript that does not need threads — it needs the first handler to yield at an await while the second one starts:

Promise.all starts both, and the first one suspends at its first await — the read. The second handler runs its own read against a row nobody has written yet. That is the production interleaving, reproduced deterministically, in a unit test, with no timing hacks.

Run that against the old code and it fails: two announcements. Which is what a test is for.

The mock database ignored the condition I was about to rely on. It recorded that an update happened and returned success; whether the row actually matched was not something it modelled. So a conditional write and an unconditional one produced identical results, and no test on earth could tell them apart.

I want to be precise about how bad this is. A missing test leaves a known hole. A test double that quietly simplifies the thing you are testing produces confident wrong answers, and it produces them in the exact area you thought you had covered. It is the same failure mode as the original bug, one level up.

So the mock had to learn the one behaviour that matters here: a conditional update is a check and a write in a single step, and the row is changed before anyone else can read it.

Now the second caller in the Promise.all sees what the first one wrote. Now the test fails on the old code and passes on the new one, which is the only property that makes a test worth keeping.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation