Skip to main content

Test Harness

The TestHarness provides utilities for testing sagas with in-memory transport and store.

Installation​

npm install -D @saga-bus/test

Basic Usage​

import { TestHarness } from '@saga-bus/test';
import { orderSaga } from './sagas/order-saga';

const harness = new TestHarness();

await harness.start({
sagas: [{ definition: orderSaga }],
});

// Publish a message
await harness.publish({
type: 'OrderSubmitted',
orderId: '123',
customerId: 'cust-456',
total: 99.99,
});

// Wait for saga to process
await harness.waitForSaga('OrderSaga', '123');

// Get saga state
const state = await harness.getSagaState('OrderSaga', '123');
expect(state.status).toBe('submitted');

await harness.stop();

Configuration​

const harness = new TestHarness({
// Process messages synchronously (default: true)
synchronous: true,

// Timeout for waitFor methods (default: 5000ms)
defaultTimeout: 5000,

// Enable debug logging
debug: false,
});

API Reference​

start(options)​

Start the harness with saga registrations.

await harness.start({
sagas: [
{ definition: orderSaga },
{ definition: paymentSaga },
],
middleware: [
createLoggingMiddleware({ level: 'debug' }),
],
});

publish(message)​

Publish a message to be processed.

await harness.publish({
type: 'OrderSubmitted',
orderId: '123',
customerId: 'cust-456',
});

waitForSaga(sagaName, correlationId, options?)​

Wait for a saga instance to reach a certain state.

// Wait for saga to exist
await harness.waitForSaga('OrderSaga', '123');

// Wait for specific status
await harness.waitForSaga('OrderSaga', '123', {
status: 'paid',
});

// Wait for completion
await harness.waitForSaga('OrderSaga', '123', {
completed: true,
});

// Custom timeout
await harness.waitForSaga('OrderSaga', '123', {
timeout: 10000,
});

waitForMessage(messageType, predicate?)​

Wait for a specific message to be published.

// Wait for any message of type
const msg = await harness.waitForMessage('PaymentRequested');

// Wait for message matching predicate
const msg = await harness.waitForMessage('PaymentRequested', (m) =>
m.orderId === '123'
);

getSagaState(sagaName, correlationId)​

Get the current state of a saga instance.

const state = await harness.getSagaState('OrderSaga', '123');
console.log(state.status); // 'paid'

getPublishedMessages(messageType?)​

Get all messages published during the test.

// All messages
const allMessages = harness.getPublishedMessages();

// Messages of specific type
const payments = harness.getPublishedMessages('PaymentRequested');

stop()​

Stop the harness and clean up resources.

await harness.stop();

Test Patterns​

Setup and Teardown​

import { TestHarness } from '@saga-bus/test';

describe('OrderSaga', () => {
let harness: TestHarness;

beforeEach(async () => {
harness = new TestHarness();
await harness.start({
sagas: [{ definition: orderSaga }],
});
});

afterEach(async () => {
await harness.stop();
});

it('test case', async () => {
// ...
});
});

Testing State Transitions​

it('transitions from pending to paid', async () => {
// Start saga
await harness.publish({
type: 'OrderSubmitted',
orderId: '123',
});
await harness.waitForSaga('OrderSaga', '123', { status: 'pending' });

// Transition to paid
await harness.publish({
type: 'PaymentCaptured',
orderId: '123',
transactionId: 'txn-1',
});
await harness.waitForSaga('OrderSaga', '123', { status: 'paid' });

const state = await harness.getSagaState('OrderSaga', '123');
expect(state.status).toBe('paid');
expect(state.transactionId).toBe('txn-1');
});

Verifying Published Messages​

it('requests payment after order submitted', async () => {
await harness.publish({
type: 'OrderSubmitted',
orderId: '123',
total: 99.99,
});

await harness.waitForMessage('PaymentRequested');

const messages = harness.getPublishedMessages('PaymentRequested');
expect(messages).toHaveLength(1);
expect(messages[0].orderId).toBe('123');
expect(messages[0].amount).toBe(99.99);
});

Testing Timeouts​

it('handles timeout', async () => {
await harness.publish({ type: 'OrderSubmitted', orderId: '123' });
await harness.waitForSaga('OrderSaga', '123');

// Advance time
await harness.advanceTime(5 * 60 * 1000); // 5 minutes

// Timeout should trigger
await harness.waitForMessage('PaymentTimeout');
});

See Also​