@temporalio/testing
Version:
Temporal.io SDK Testing sub-package
259 lines (258 loc) • 11.7 kB
TypeScript
/**
* `npm i @temporalio/testing`
*
* Testing library for the SDK.
*
* [Documentation](https://docs.temporal.io/typescript/testing)
*
* @module
*/
import 'abort-controller/polyfill';
import events from 'node:events';
import * as activity from '@temporalio/activity';
import { AsyncCompletionClient, Client, ClientOptions, WorkflowClient, WorkflowClientOptions, WorkflowResultOptions } from '@temporalio/client';
import { ActivityFunction, Duration, Logger } from '@temporalio/common';
import { ActivityInterceptorsFactory, NativeConnection } from '@temporalio/worker';
import { EphemeralServer, EphemeralServerConfig, DevServerConfig, TimeSkippingServerConfig } from '@temporalio/core-bridge';
import { Connection, TestService } from './connection';
export { TimeSkippingServerConfig, DevServerConfig, EphemeralServerExecutable } from '@temporalio/core-bridge';
export { EphemeralServerConfig };
export interface TimeSkippingWorkflowClientOptions extends WorkflowClientOptions {
connection: Connection;
enableTimeSkipping: boolean;
}
export interface TestEnvClientOptions extends ClientOptions {
connection: Connection;
enableTimeSkipping: boolean;
}
/**
* A client with the exact same API as the "normal" client with 1 exception,
* When this client waits on a Workflow's result, it will enable time skipping
* in the test server.
*/
export declare class TimeSkippingWorkflowClient extends WorkflowClient {
protected readonly testService: TestService;
protected readonly enableTimeSkipping: boolean;
constructor(options: TimeSkippingWorkflowClientOptions);
/**
* Gets the result of a Workflow execution.
*
* @see {@link WorkflowClient.result}
*/
result<T>(workflowId: string, runId?: string | undefined, opts?: WorkflowResultOptions | undefined): Promise<T>;
}
/**
* Convenience workflow interceptors
*
* Contains a single interceptor for transforming `AssertionError`s into non
* retryable `ApplicationFailure`s.
*/
export declare const workflowInterceptorModules: string[];
/**
* Subset of the "normal" client options that are used to create a client for the test environment.
*/
export type ClientOptionsForTestEnv = Omit<ClientOptions, 'namespace' | 'connection'>;
/**
* Options for {@link TestWorkflowEnvironment.create}
*/
export type TestWorkflowEnvironmentOptions = {
server: EphemeralServerConfig;
client?: ClientOptionsForTestEnv;
};
/**
* Options for {@link TestWorkflowEnvironment.createTimeSkipping}
*/
export type TimeSkippingTestWorkflowEnvironmentOptions = {
server?: Omit<TimeSkippingServerConfig, 'type'>;
client?: ClientOptionsForTestEnv;
};
/**
* Options for {@link TestWorkflowEnvironment.createLocal}
*/
export type LocalTestWorkflowEnvironmentOptions = {
server?: Omit<DevServerConfig, 'type'>;
client?: ClientOptionsForTestEnv;
};
export type TestWorkflowEnvironmentOptionsWithDefaults = Required<TestWorkflowEnvironmentOptions>;
/**
* An execution environment for running Workflow integration tests.
*
* Runs an external server.
* By default, the Java test server is used which supports time skipping.
*/
export declare class TestWorkflowEnvironment {
readonly options: TestWorkflowEnvironmentOptionsWithDefaults;
readonly supportsTimeSkipping: boolean;
protected readonly server: EphemeralServer;
/**
* Namespace used in this environment (taken from {@link TestWorkflowEnvironmentOptions})
*/
readonly namespace?: string;
/**
* Get an established {@link Connection} to the ephemeral server
*/
readonly connection: Connection;
/**
* A {@link TestEnvClient} for interacting with the ephemeral server
*/
readonly client: Client;
/**
* An {@link AsyncCompletionClient} for interacting with the test server
*
* @deprecated - use `client.activity` instead
*/
readonly asyncCompletionClient: AsyncCompletionClient;
/**
* A {@link TimeSkippingWorkflowClient} for interacting with the test server
*
* @deprecated - use `client.workflow` instead
*/
readonly workflowClient: WorkflowClient;
/**
* A {@link NativeConnection} for interacting with the test server.
*
* Use this connection when creating Workers for testing.
*/
readonly nativeConnection: NativeConnection;
protected constructor(options: TestWorkflowEnvironmentOptionsWithDefaults, supportsTimeSkipping: boolean, server: EphemeralServer, connection: Connection, nativeConnection: NativeConnection, namespace: string | undefined);
/**
* Start a time skipping workflow environment.
*
* This environment automatically skips to the next events in time when a workflow handle's `result` is awaited on
* (which includes {@link WorkflowClient.execute}). Before the result is awaited on, time can be manually skipped
* forward using {@link sleep}. The currently known time can be obtained via {@link currentTimeMs}.
*
* This environment will be powered by the Temporal Time Skipping Test Server (part of the [Java SDK](https://github.com/temporalio/sdk-java)).
* Note that the Time Skipping Test Server does not support full capabilities of the regular Temporal Server, and may
* occasionally present different behaviors. For general Workflow testing, it is generally preferable to use {@link createLocal}
* instead.
*
* Users can reuse this environment for testing multiple independent workflows, but not concurrently. Time skipping,
* which is automatically done when awaiting a workflow result and manually done on sleep, is global to the
* environment, not to the workflow under test. We highly recommend running tests serially when using a single
* environment or creating a separate environment per test.
*
* By default, the latest release of the Test Serveer will be downloaded and cached to a temporary directory
* (e.g. `$TMPDIR/temporal-test-server-sdk-typescript-*` or `%TEMP%/temporal-test-server-sdk-typescript-*.exe`). Note
* that existing cached binairies will be reused without validation that they are still up-to-date, until the SDK
* itself is updated. Alternatively, a specific version number of the Test Server may be provided, or the path to an
* existing Test Server binary may be supplied; see {@link LocalTestWorkflowEnvironmentOptions.server.executable}.
*
* Note that the Test Server implementation may be changed to another one in the future. Therefore, there is no
* guarantee that Test Server options, and particularly those provided through the `extraArgs` array, will continue to
* be supported in the future.
*
* IMPORTANT: At this time, the Time Skipping Test Server is not supported on ARM platforms. Execution on Apple
* silicon Macs will work if Rosetta 2 is installed.
*/
static createTimeSkipping(opts?: TimeSkippingTestWorkflowEnvironmentOptions): Promise<TestWorkflowEnvironment>;
/**
* Start a full Temporal server locally.
*
* This environment is good for testing full server capabilities, but does not support time skipping like
* {@link createTimeSkipping} does. {@link supportsTimeSkipping} will always return `false` for this environment.
* {@link sleep} will sleep the actual amount of time and {@link currentTimeMs} will return the current time.
*
* This local environment will be powered by [Temporal CLI](https://github.com/temporalio/cli), which is a
* self-contained executable for Temporal. By default, Temporal's database will not be persisted to disk, and no UI
* will be launched.
*
* By default, the latest release of the CLI will be downloaded and cached to a temporary directory
* (e.g. `$TMPDIR/temporal-sdk-typescript-*` or `%TEMP%/temporal-sdk-typescript-*.exe`). Note that existing cached
* binairies will be reused without validation that they are still up-to-date, until the SDK itself is updated.
* Alternatively, a specific version number of the CLI may be provided, or the path to an existing CLI binary may be
* supplied; see {@link LocalTestWorkflowEnvironmentOptions.server.executable}.
*
* Note that the Dev Server implementation may be changed to another one in the future. Therefore, there is no
* guarantee that Dev Server options, and particularly those provided through the `extraArgs` array, will continue to
* be supported in the future.
*/
static createLocal(opts?: LocalTestWorkflowEnvironmentOptions): Promise<TestWorkflowEnvironment>;
/**
* Create a new test environment
*/
private static create;
/**
* Kill the test server process and close the connection to it
*/
teardown(): Promise<void>;
/**
* Wait for `durationMs` in "server time".
*
* This awaits using regular setTimeout in regular environments, or manually skips time in time-skipping environments.
*
* Useful for simulating events far into the future like completion of long running activities.
*
* **Time skippping**:
*
* The time skippping server toggles between skipped time and normal time depending on what it needs to execute.
*
* This method is _likely_ to resolve in less than `durationMs` of "real time".
*
* @param durationMs number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}
*
* @example
*
* `workflow.ts`
*
* ```ts
* const activities = proxyActivities({ startToCloseTimeout: 2_000_000 });
*
* export async function raceActivityAndTimer(): Promise<string> {
* return await Promise.race([
* wf.sleep(500_000).then(() => 'timer'),
* activities.longRunning().then(() => 'activity'),
* ]);
* }
* ```
*
* `test.ts`
*
* ```ts
* const worker = await Worker.create({
* connection: testEnv.nativeConnection,
* activities: {
* async longRunning() {
* await testEnv.sleep(1_000_000); // <-- sleep called here
* },
* },
* // ...
* });
* ```
*/
sleep: (durationMs: Duration) => Promise<void>;
/**
* Get the current time known to this environment.
*
* For non-time-skipping environments this is simply the system time. For time-skipping environments this is whatever
* time has been skipped to.
*/
currentTimeMs(): Promise<number>;
}
export interface MockActivityEnvironmentOptions {
interceptors?: ActivityInterceptorsFactory[];
logger?: Logger;
}
/**
* Used as the default activity info for Activities executed in the {@link MockActivityEnvironment}
*/
export declare const defaultActivityInfo: activity.Info;
/**
* An execution environment for testing Activities.
*
* Mocks Activity {@link Context | activity.Context} and exposes hooks for cancellation and heartbeats.
*
* Note that the `Context` object used by this environment will be reused for all activities that are run in this
* environment. Consequently, once `cancel()` is called, any further activity that gets executed in this environment
* will immediately be in a cancelled state.
*/
export declare class MockActivityEnvironment extends events.EventEmitter {
cancel: (reason?: any) => void;
readonly context: activity.Context;
private readonly activity;
constructor(info?: Partial<activity.Info>, opts?: MockActivityEnvironmentOptions);
/**
* Run a function in Activity Context
*/
run<P extends any[], R, F extends ActivityFunction<P, R>>(fn: F, ...args: P): Promise<R>;
}