Test a race condition without relying on a lucky manual reproduction

A race condition can survive repeated manual testing because its failing interleaving is rare. Adding a delay and clicking twice may reproduce it once, then disappear in CI. The regression needs control over execution order and an assertion about the invariant that must hold under concurrency.
Consider an illustrative invite redemption flow. Two requests read an unused invitation and both attempt to create membership. The intended result is one valid redemption, not two independently successful operations. The database mechanism matters because application-level checks can overlap. This article describes a deterministic test strategy; it does not claim that a particular database or unexecuted snippet has been verified.
Identify the invariant and competing operations
State the business rule in storage terms. One invitation may transition from unused to redeemed once, and membership creation must agree with that transition. List the operations that compete for the same state. Separate duplicate transport attempts from independent valid requests, because their expected responses may differ.
Trace the vulnerable sequence: read state, decide eligible, write state, perform effect. Mark the point where both requests can observe the same old value. This is the interleaving the test should expose. Do not begin with an arbitrary sleep duration. Begin with the shared state and the unsafe gap. A clear sequence makes it possible to test both the broken behavior and the corrected atomic mechanism.
Choose the boundary that owns atomicity
If the fix uses a conditional database update or uniqueness constraint, test against a real isolated instance of that database. A fake repository that serializes calls automatically can conceal the defect. Unit tests still help response mapping, but they cannot establish the storage invariant.
Use synthetic invitations and accounts scoped to the test run. Provision the required database and make unavailable infrastructure fail clearly rather than skipping the case. Record relevant database and driver versions. A practical artifact names the invariant, shared record, competing operations, coordination point, and expected final state. It tells reviewers why this integration test exists and what a green result can legitimately prove.
Coordinate the failing interleaving
Use a barrier or controllable hook around the vulnerable decision boundary in the isolated test. Allow both requests to reach the read or eligibility stage, then release them to attempt the write. Keep the hook out of normal production behavior or inject it through a narrow test seam. Avoid adding broad delay flags to the public API.
For a corrected operation that no longer has the separate read gap, coordinate concurrent starts and rely on the actual conditional write to arbitrate. Do not force an artificial interleaving that bypasses the fix's atomic path. The test should exercise the public or application operation while using control only to make the relevant scheduling predictable. Bound waits and provide diagnostic failure if a barrier is never reached.
Assert durable state and allowed outcomes
Inspect the invitation state and memberships after both operations settle. Assert that the invariant holds and that no duplicate consequential effect occurred. Response expectations should reflect the contract: one request may succeed and another report already redeemed, or both may return the same idempotent result if that is the intended behavior.
Do not assert which request wins unless order is part of the contract. Scheduling may choose either. Avoid checking only that one promise rejects, because a rejected request could still have mutated state. Verify the final durable record and relevant effect identity. If the flow publishes messages, inspect event identity and duplicate handling separately. Correct response status is useful but insufficient evidence of concurrency safety.
Prove the regression targets the defect
Where feasible, run the coordinated test against pre-fix behavior in an isolated checkout or controlled temporary experiment. Confirm the expected duplicate or invalid transition appears. Then run against the fix and inspect the invariant. Do not destructively reset unrelated work to make this comparison.
If a pre-fix run is unavailable, record the limitation and explain the vulnerable sequence from code evidence. Keep the test capable of failing for the actual invariant rather than setup errors. A missing database, unreachable barrier, or authentication failure is not proof that the regression caught the race. Distinguish infrastructure failure from behavioral failure in the test output so reviewers can assess the result correctly.
Test related interruption cases separately
Concurrency and crash recovery are related but distinct. A process can stop after consuming the invitation but before creating membership if the writes are not atomic. Add a focused interruption or transaction case where that risk exists. Do not assume a concurrency test establishes crash consistency.
Also consider retry after a response is lost. The operation may have completed even though the client saw a timeout. Its repeat behavior should be explicit. Link single-use reset-token design for another one-time transition. Keep each case tied to a distinct failure mode instead of turning one test into an elaborate scenario that is difficult to diagnose when it fails.
When the invariant spans several records, inspect all of them after failure as well as success. An invitation marked redeemed with no membership is a different defect from two memberships with one redemption. Make the assertion describe the allowed combined state. If the database rolls back a transaction, verify the actual persisted outcome rather than assuming a thrown exception means every write was undone.
Keep scheduling tests reliable in CI
Use deterministic coordination, bounded timeouts, and cleanup of synthetic records. Ensure all promises are awaited and unexpected errors fail the test. Release barriers during failure cleanup so a failed assertion does not leave the test process hanging. Avoid shared identifiers across parallel runs.
A repeated stress test may supplement the deterministic case, but it should not be the only regression. Stress testing samples many schedules and can reveal additional problems; the coordinated case targets a known vulnerable sequence. Record the scope of each. If the database's transaction configuration differs from production, state that limitation and add appropriate environment verification before claiming the deployed invariant is protected.
Use AI to enumerate interleavings
Give an assistant the operation sequence, storage calls, and intended invariant. Ask it to list concrete interleavings and identify which must be prevented by an atomic mechanism. Require it to distinguish JavaScript scheduling from database concurrency and distributed worker execution.
Have it draft a coordination plan and assertions before code. Review generated hooks to ensure they do not create public production controls or bypass the actual operation. Verify library APIs against installed documentation. AI can help expose missed crash and retry cases, but the final concurrency claim must come from executed tests and the database's real conditional-write or transaction semantics, not from a promising-looking test harness.
Sources
Node's test runner documentation provides the asynchronous execution and cleanup reference.
MongoDB atomicity and transaction documentation provides a concrete primary reference for a storage boundary where single-document conditional writes and multi-document transactions differ. Verify the equivalent semantics in your chosen database. The invitation interleaving is illustrative.
About the Author

Kamruz
Full Stack Developer
A passionate developer who loves creating modern web applications and sharing knowledge with the community.
More About Me