← Recipes

阅读中文版本

Verify Database Transaction Rollback

Assert that a failed multi-step write leaves no partial state, using a real database in the test rather than mocks.

~35 mintestingdatabasetransactions

Task

Test that a transaction rolls back completely when a later step fails.

When to use this

A partial write survives a failed operation, leaving inconsistent records that break reconciliation.

Prerequisites

  • A real or containerised database in tests
  • A repository layer using transactions
  • A way to force a mid-transaction failure

Steps

1. Run the write with an injected failure in the second step.

await expect(service.transfer({ from: 'a', to: 'b', amount: 10, failAfter: 1 })).rejects.toThrow();

Expected output: The operation rejects, signalling the transaction did not commit.

2. Assert both accounts are unchanged.

expect(await balance('a')).toBe(100);
expect(await balance('b')).toBe(50);

Expected output: Neither the debit nor the credit persisted, proving atomic rollback.

Troubleshooting

Problem

Test database persists between runs

Solution

Wrap each test in a transaction that rolls back, or recreate schema per run.

Problem

Mocks hide the bug

Solution

Use a real database engine; mock repositories cannot prove transaction semantics.

Limitations

  • Requires a real database in the test environment.
  • Does not cover distributed transactions across services.
  • Slower than in-memory doubles.

What this recipe does

Test that a transaction rolls back completely when a later step fails.

A partial write survives a failed operation, leaving inconsistent records that break reconciliation.

Prerequisites

  • A real or containerised database in tests
  • A repository layer using transactions
  • A way to force a mid-transaction failure

Procedure

  1. Run the write with an injected failure in the second step.
await expect(service.transfer({ from: 'a', to: 'b', amount: 10, failAfter: 1 })).rejects.toThrow();

Expected result: The operation rejects, signalling the transaction did not commit.

  1. Assert both accounts are unchanged.
expect(await balance('a')).toBe(100);
expect(await balance('b')).toBe(50);

Expected result: Neither the debit nor the credit persisted, proving atomic rollback.

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 after the injected failure both tables are byte-for-byte unchanged, and removing the transaction makes the test fail.

Troubleshooting

Test database persists between runs — Wrap each test in a transaction that rolls back, or recreate schema per run.

Mocks hide the bug — Use a real database engine; mock repositories cannot prove transaction semantics.

Limitations

  • Requires a real database in the test environment.
  • Does not cover distributed transactions across services.
  • Slower than in-memory doubles.