In this lesson, you will learn how to verify that your distributed system correctly undoes partial progress when a multi-service transaction fails.
When you coordinate state across multiple microservices—such as updating an inventory service, a payment service, and a shipping service—you cannot use traditional database ACID transactions. Instead, we use the Saga Pattern, where each local transaction publishes an event to trigger the next step. If any step fails, the system must execute "compensating transactions" to roll back the previous, successful steps.
The Testing Challenge
Testing Sagas is challenging because the failure can happen anywhere in the chain. You aren't just testing the happy path; you are verifying that the orchestrator or choreography logic correctly triggers the compensation handlers.
Illustrating the Compensation Flow
Imagine a checkout process. If the PaymentService fails, the InventoryService must receive a RESTORE_STOCK command. In your integration test, you should simulate this by forcing a failure in a downstream service and asserting the state change in the upstream service.
// Integration test snippet using a mock event bus
it('should restore stock when payment fails', async () => {
// 1. Arrange: Setup inventory
await inventoryService.updateStock('item-123', -1);
// 2. Act: Trigger payment failure (simulated via mock)
await paymentGateway.simulateFailure();
await checkoutSaga.process({ orderId: 'ord-999' });
// 3. Assert: Verify compensation occurred
const stock = await inventoryService.getStock('item-123');
expect(stock).toBe(originalStockCount);
});
Best Practices for Verification
- Idempotency Checks: Since network retries are common in distributed systems, ensure your compensation logic can run multiple times without causing side effects (e.g., adding stock twice).
- State Transition Monitoring: Use a test-specific state observer to ensure the saga reached the
COMPENSATINGstate before finishing. - Partial Failure Injection: Use your knowledge of "Simulating Network Latency and Failures" (Lesson 24) to drop connections specifically after the first service succeeds but before the second begins.
Key Takeaway: Testing a Saga is fundamentally about verifying that every forward action has a corresponding, tested, and idempotent compensation action that returns the system to a consistent state upon failure.