Skip to content

testurio (Core)

The core testurio package contains the framework engine, all component types, the execution layer, the hook system, and the built-in HTTP protocol.

bash
npm install testurio --save-dev

Exports

typescript
import {
  // Components
  Client,
  Server,
  AsyncClient,
  AsyncServer,
  DataSource,
  Publisher,
  Subscriber,

  // Execution
  TestScenario,
  testCase,

  // Protocols
  HttpProtocol,

  // Codecs
  JsonCodec,
} from 'testurio';

TestScenario

Orchestrates component lifecycle and test execution.

Constructor

typescript
new TestScenario(options: TestScenarioOptions)
OptionTypeDescription
namestringScenario name (used in reports)
componentsComponent[]Components in startup order (servers before clients)
reportersIReporter[](optional) Test reporters

Methods

MethodDescription
run(...testCases)Run one or more test cases. Returns Promise<TestResult>
init(handler)Register setup that runs after component startup
stop(handler)Register teardown that runs before component shutdown
addReporter(reporter)Add a test reporter

TestResult

typescript
interface TestResult {
  name: string;
  passed: boolean;
  testCases: TestCaseResult[];
  duration: number;
}

testCase

Factory function for creating test cases.

typescript
const tc = testCase(name: string, builder: (test: TestContext) => void)

TestContext

MethodReturnsDescription
use(client)SyncClientStepBuilderGet step builder for a Client
use(server)SyncServerStepBuilderGet step builder for a Server
use(asyncClient)AsyncClientStepBuilderGet step builder for an AsyncClient
use(asyncServer)AsyncServerStepBuilderGet step builder for an AsyncServer
use(dataSource)DataSourceStepBuilderGet step builder for a DataSource
use(publisher)PublisherStepBuilderGet step builder for a Publisher
use(subscriber)SubscriberStepBuilderGet step builder for a Subscriber
wait(ms)voidAdd a wait step
waitUntil(fn, options?)voidWait until a condition is true

TestCase Metadata

typescript
testCase('name', (test) => { /* ... */ })
  .id('TC-001')
  .epic('Epic Name')
  .feature('Feature Name')
  .story('Story Name')
  .severity('critical')
  .tags('api', 'smoke')
  .issue('BUG-123')
  .description('Description text')
  .before((test) => { /* setup */ })
  .after((test) => { /* teardown */ });

Client

Sends synchronous requests to a target server.

typescript
new Client(name: string, options: ClientOptions)
OptionTypeDescription
protocolISyncProtocolSync protocol instance
targetAddressAddressServer address
validationSyncValidationOptions(optional) Auto-validation settings

Step Builder Methods

MethodModeDescription
request(operationId, data, traceId?)actionSend a request. Chain .retry(...) to poll until the response matches a predicate
onResponse(operationId, traceId?)hookNon-blocking response handler
waitResponse(operationId, options?)waitBlock until response arrives

Request Builder Chain Methods

Returned by request(...):

MethodDescription
.onResponse()Shorthand for the matching onResponse(operationId) step
.retry(predicate, timeoutMs?)Poll: re-fire the request while predicate returns true. Defaults: timeout = 5000 ms, interval = 1000 ms, retryOnError = true. See Hook Builder Methods for the options form. Terminal response is delivered to matching onResponse/waitResponse hooks.

Server

Mock server or proxy.

typescript
new Server(name: string, options: ServerOptions)
OptionTypeDescription
protocolISyncProtocolSync protocol instance
listenAddressAddressAddress to listen on
targetAddressAddress(optional) Backend for proxy mode
validationSyncValidationOptions(optional) Auto-validation settings

Step Builder Methods

MethodModeDescription
onRequest(operationId, matcher?)hookHandle incoming request
waitRequest(operationId, matcher?)waitBlock until request arrives

AsyncClient

Sends messages over persistent connections. Connection can be deferred with autoConnect: false (default) and established explicitly via connect().

typescript
new AsyncClient(name: string, options: AsyncClientOptions)
OptionTypeDescription
protocolIAsyncProtocolAsync protocol instance
targetAddressAddressServer address
validationAsyncValidationOptions(optional) Auto-validation settings
autoConnectboolean | ProtocolConnectParams<P>(optional) Connection control. false (default): requires explicit connect() step. true: auto-connect without params. Object: auto-connect with protocol-typed params (e.g., { headers: { ... } } for WS, { metadata: { ... } } for gRPC)

Step Builder Methods

MethodModeDescription
connect(params?)actionEstablish connection. Accepts protocol-typed params or factory function
sendMessage(messageType, data, traceId?)actionSend a message
onEvent(messageType, matcher?)hookNon-blocking event handler
waitEvent(messageType, options?)waitBlock until event arrives
disconnect()actionClose the connection
waitDisconnect()waitBlock until connection closes

AsyncServer

Mock async server or proxy.

typescript
new AsyncServer(name: string, options: AsyncServerOptions)
OptionTypeDescription
protocolIAsyncProtocolAsync protocol instance
listenAddressAddressAddress to listen on
targetAddressAddress(optional) Backend for proxy mode
validationAsyncValidationOptions(optional) Auto-validation settings

Step Builder Methods

MethodModeDescription
onConnection(linkId, options?)hookLink connection when it arrives
waitConnection(linkId, options?)waitBlock until client connects
onMessage(messageType, options?)hookHandle incoming message
waitMessage(messageType, options?)waitBlock until message arrives
onEvent(eventType)hookHandle backend event (proxy mode)
waitEvent(eventType, options?)waitBlock until backend event arrives (proxy mode)
sendEvent(linkId, eventType, payload)actionSend event to linked connection
broadcast(eventType, payload)actionSend event to all connections
disconnect(linkId)actionDisconnect a linked connection
onDisconnect(linkId, handler)hookHandle linked connection disconnect
waitDisconnect(linkId)waitBlock until client disconnects

DataSource

Direct SDK access to databases/caches.

typescript
new DataSource(name: string, options: DataSourceOptions)
OptionTypeDescription
adapterIDataSourceAdapterDataSource adapter instance

Step Builder Methods

MethodModeDescription
exec(description, fn, options?)actionExecute operations on the data store. Returns a hook builder that supports .assert(...), .timeout(ms), and .retry(...) for polling.

Publisher

Publishes messages to message queue topics.

typescript
new Publisher(name: string, options: PublisherOptions)
OptionTypeDescription
adapterIMQAdapterMQ adapter instance
schemaMQSchemaInput(optional) Topic-based schemas
validationMQValidationOptions(optional) Auto-validation settings

Step Builder Methods

MethodModeDescription
publish(topic, data, options?)actionPublish a message
publishBatch(topic, messages)actionPublish multiple messages

Subscriber

Subscribes to messages from message queue topics.

typescript
new Subscriber(name: string, options: SubscriberOptions)
OptionTypeDescription
adapterIMQAdapterMQ adapter instance
schemaMQSchemaInput(optional) Topic-based schemas
validationMQValidationOptions(optional) Auto-validation settings
autoSubscribeboolean(optional, default true) Whether Phase 1.5 auto-subscribes to hook-derived topics. See below.

Migration from masteradapter previously took an already-materialized IMQSubscriberAdapter, and autoSubscribe accepted true \| string[]. Both have changed. The Subscriber now materializes a fresh per-test-case adapter on every TC.

autoSubscribe modes

ModeBehavior
trueDefault. Phase 1.5 issues a single batched adapter.subscribe([...]) for every topic referenced by onMessage / waitMessage / waitMessageFrom hooks in the TC.
falseImperative-only — the test must call ev.subscribe(...) explicitly. The empty-array shortcut ev.subscribe() subscribes to all hook-derived topics for the TC.

Adapters activate their delivery loop on first subscribe (master's separate startConsuming is gone — folded in). For Kafka this means one consumer.subscribe + consumer.run cycle per TC, with GROUP_JOIN awaited before resolving.

Step Builder Methods

MethodModeDescription
onMessage(topic, options?)hookNon-blocking message handler. Auto-subscribes the topic at Phase 1.5 (when autoSubscribe: true).
waitMessage(topic, options?)waitBlock until a message arrives. Auto-subscribes the topic at Phase 1.5.
waitMessageFrom(topics, options?)waitBlock until a message arrives on any of the given topics.
subscribe(topic?, params?)actionImperative subscribe. topic: single, array, or omitted (shortcut → all hook-derived). params: adapter-specific overrides.
unsubscribe(topic?)actionImperative unsubscribe. topic: single, array, or omitted (shortcut → all currently-held).

Footgun — the empty-array shortcut on subscribe([]) / unsubscribe([]) subscribes / unsubscribes from "all of them". When callers spread a computed array that may be empty (ev.subscribe(computedTopics) where computedTopics: string[]), guard at the call site: if (computedTopics.length > 0) ev.subscribe(computedTopics);

Persistent / scenario-level hooks

Subscriber.registerHook throws when step.testCaseId === undefined — that covers hooks registered outside a testCase() body, and hooks registered inside scenario.init / scenario.stop handlers. There is no scenario-level subscription primitive in testurio. Move the hook into a testCase() body.

Performance — parallel test cases

Each test case requires one broker coordinator-join (when a Subscriber is used in the TC). For N parallel TCs that means N concurrent join handshakes against a single broker partition leader.

Recommended parallel-TC cap depends on broker group.initial.rebalance.delay.ms:

  • With KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0 (recommended for testing — see Kafka test-broker config): 8 – 16 parallel TCs on a single broker partition leader.
  • With default group.initial.rebalance.delay.ms=3000: ~3 parallel TCs to avoid coordinator-join contention.

For higher fan-out, use a shared groupId opt-out (new KafkaAdapter({ defaultSubscribeParams: { groupId: 'shared' } })) and accept Kafka partition-assignment semantics.

HttpProtocol

Built-in HTTP protocol using Express server and fetch client.

typescript
// Loose mode
new HttpProtocol()

// Explicit generic mode
new HttpProtocol<ServiceDef>()

// Schema-first mode
new HttpProtocol({ schema: zodSchemas })

Hook Builder Methods

All hook builders support these chaining methods:

MethodDescription
.assert(fn)Validate payload
.assert(description, fn)Validate with named assertion
.transform(fn)Transform payload
.delay(ms)Add delay
.drop()Drop message
.mockResponse(fn)Mock response (sync server only)
.mockEvent(type, fn)Send event (async server only)
.proxy(fn?)Forward to backend (proxy mode)
.validate()Validate against schema
.validate(schema)Validate against explicit schema
.timeout(ms)Set timeout (wait steps only)
.retry(predicate, opts?)Poll the action: re-run until predicate returns false, with overall timeout (DataSource exec only — see Polling & Retry guide)

Types

Address

typescript
interface Address {
  host: string;
  port: number;
  path?: string;
}

SchemaLike

typescript
interface SchemaLike<T> {
  parse(data: unknown): T;
}

ValidationError

typescript
class ValidationError extends Error {
  componentName: string;
  operationId: string;
  direction: string;
  cause: Error;
}

JsonCodec

typescript
new JsonCodec(options?: {
  reviver?: (key: string, value: unknown) => unknown;
  replacer?: (key: string, value: unknown) => unknown;
})

RetryPredicate

typescript
type RetryPredicate<T> = (result: T) => boolean | Promise<boolean>;

Returns true to keep retrying, false to stop. See the Polling & Retry guide for full semantics.

RetryOptions

typescript
interface RetryOptions {
  /** Overall wall-clock timeout for the polling loop. Default: 5000. */
  timeout?: number;
  /** Delay between attempts in ms. Default: 1000. Use 0 for immediate retry. */
  interval?: number;
  /** Treat thrown attempts as "not ready" and retry. Default: true. */
  retryOnError?: boolean;
}

RetryTimeoutError

typescript
class RetryTimeoutError extends Error {
  readonly attempts: number;
  readonly elapsedMs: number;
  readonly lastResult: unknown;
  readonly lastError: Error | undefined;
}

Thrown by .retry(...) when the overall timeout elapses without the predicate returning false. The lastResult is the most recent attempt result (or undefined if every attempt threw); lastError is the most recent thrown error (or undefined if no attempt threw).

Released under the MIT License.