Testing utilities for temporal-contract integration tests
pnpm add -D @temporal-contract/testingThe package declares peer dependencies on the other three @temporal-contract/* packages (contract, client, worker — the contract-aware fixtures hand you a TypedClient and run a worker, so they must resolve to your copies), plus @temporalio/client, @temporalio/testing, @temporalio/worker, unthrown (^5), and vitest (^4). Make sure they are installed alongside it — see Install temporal-contract for the full matrix.
Configure Vitest to start a Temporal server (via testcontainers — requires Docker) before all tests:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globalSetup: "@temporal-contract/testing/global-setup",
testTimeout: 60000,
},
});To pin container images, inject extra Temporal env, or silence the progress logs, reference your own module that default-exports createGlobalSetup(options):
// temporal-global-setup.ts
import { createGlobalSetup } from "@temporal-contract/testing/global-setup";
export default createGlobalSetup({
temporalImage: "temporalio/auto-setup:1.28.0",
quiet: true,
});createContractTest wires the whole stack for one contract — a running worker, the connection-scoped TypedClient root, and the contract-bound ContractClient:
// order.spec.ts
import { createContractTest } from "@temporal-contract/testing/contract";
import { declareActivitiesHandler } from "@temporal-contract/worker/activity";
import { workflowsPathFromURL } from "@temporal-contract/worker/worker";
import { describe, expect } from "vitest";
import { orderContract } from "./order.contract.js";
const activities = declareActivitiesHandler({ contract: orderContract, activities: { ... } });
const it = createContractTest(orderContract, {
workflowsPath: workflowsPathFromURL(import.meta.url, "./order.workflows.js"),
activities, // omit for a workflow-only worker
});
describe("order processing", () => {
it("processes an order", async ({ client, typedClient, worker }) => {
const result = await client.executeWorkflow("processOrder", {
workflowId: `order-${Date.now()}`,
args: { orderId: "ORD-1" },
});
expect(result.isOk()).toBe(true);
});
});runActivity executes one AsyncResult-returning activity implementation inside @temporalio/testing's MockActivityEnvironment — no worker, no server, no Docker. Pass your own environment to observe heartbeats or trigger cancellation:
import { runActivity } from "@temporal-contract/testing/activity";
const result = await runActivity(chargeCardDefinition, chargeCard, { amount: 100 });
expect(result.isOk()).toBe(true);The ./time-skipping entry runs suites against Temporal's in-process time-skipping test server (downloaded and cached by @temporalio/testing on first use) — hour-long timers resolve instantly:
import { it } from "@temporal-contract/testing/time-skipping";
// or pin the server version:
// const it = createTimeSkippingTest({ server: { executable: { type: "cached-download", version: "v1.3.0" } } });
it("processes the order", async ({ testEnv }) => {
// testEnv.client, testEnv.nativeConnection, worker.runUntil(...)
});createTimeSkippingEnvironment(options?) creates the environment directly for suites preferring explicit beforeAll/afterAll management.
Use the it extension for automatic connection management against the testcontainers server:
// my-workflow.spec.ts
import { it } from "@temporal-contract/testing/extension";
import { Client } from "@temporalio/client";
import { expect } from "vitest";
it("should execute workflow", async ({ clientConnection, workerConnection }) => {
// clientConnection: Connection from @temporalio/client (auto-connected, auto-closed)
// workerConnection: NativeConnection from @temporalio/worker (auto-connected, auto-closed)
const client = new Client({ connection: clientConnection });
// ... use client and workerConnection in your test
});📖 Read the full documentation →
MIT