temporal-ts-testing-suite

star 0

Use when writing tests for Temporal TypeScript Workflows and Activities, including unit tests, integration tests, time-skipping tests, mocking Activities, testing heartbeats and cancellation, asserting in Workflow context, and replaying Workflow Event Histories for determinism checks. Covers TestWorkflowEnvironment, MockActivityEnvironment, and Worker replay APIs from @temporalio/testing.

remodlai By remodlai schedule Updated 3/3/2026

name: temporal-ts-testing-suite description: > Use when writing tests for Temporal TypeScript Workflows and Activities, including unit tests, integration tests, time-skipping tests, mocking Activities, testing heartbeats and cancellation, asserting in Workflow context, and replaying Workflow Event Histories for determinism checks. Covers TestWorkflowEnvironment, MockActivityEnvironment, and Worker replay APIs from @temporalio/testing. version: 1.0.0

Temporal TypeScript Testing Suite

Instructions for writing tests for Temporal TypeScript Workflows and Activities using @temporalio/testing, including unit tests, integration tests, time-skipping, mocking, and replay-based determinism checks.

When to Use

Use this skill when you need to:

  • Test an Activity function in isolation with MockActivityEnvironment
  • Test a Workflow with real or mocked Activities using TestWorkflowEnvironment
  • Test workflows that use long sleeps or timers via time-skipping
  • Verify Workflow determinism after code changes with replay APIs
  • Test heartbeat behavior or cancellation handling in Activities
  • Assert inside Workflow sandbox code with workflowInterceptorModules
Scenario Tool
Test an Activity function in isolation MockActivityEnvironment
Test a Workflow with real or mocked Activities TestWorkflowEnvironment + Worker
Test a Workflow that uses long sleeps or timers Time-skipping (createTimeSkipping)
Verify Workflow determinism after code changes Worker.runReplayHistory / runReplayHistories
Test heartbeat behavior in an Activity MockActivityEnvironment + on('heartbeat')
Test Activity cancellation handling MockActivityEnvironment.cancel()
Assert inside Workflow sandbox code workflowInterceptorModules

How to Use

Environment Context Gathering

Before writing or running tests, confirm the following with the user:

  1. Where is Temporal running? (local dev server, Docker Compose, Kubernetes, Temporal Cloud)
  2. If not local, what is the full Temporal server URL? (e.g., my-namespace.tmprl.cloud:7233)
  3. Is ingress set up for the Temporal HTTP API? (needed if tests must reach the server from CI or a remote machine)

For local testing with TestWorkflowEnvironment, no external server is needed -- it starts an in-memory test server automatically.

Overview

The @temporalio/testing package provides:

  • TestWorkflowEnvironment -- an in-memory Temporal server with time-skipping support for Workflow integration tests.
  • MockActivityEnvironment -- a lightweight harness to run Activities in isolation with mocked context, heartbeat listening, and cancellation.
  • Replay APIs -- Worker.runReplayHistory and Worker.runReplayHistories to verify Workflow determinism against saved Event Histories.

Install:

npm install @temporalio/testing

Quick Reference

API Import Purpose
TestWorkflowEnvironment.createTimeSkipping() @temporalio/testing Start in-memory test server with time skip
testEnv.client (from env) Client connected to test server
testEnv.nativeConnection (from env) NativeConnection for creating Workers
testEnv.sleep(duration) (from env) Manually advance test server time
testEnv.teardown() (from env) Shut down test server
MockActivityEnvironment @temporalio/testing Run Activity with mocked context
env.run(activityFn, ...args) (from MockActivityEnvironment) Execute Activity function
env.cancel() (from MockActivityEnvironment) Cancel the Activity context
env.on('heartbeat', cb) (from MockActivityEnvironment) Listen for heartbeat events
Worker.runReplayHistory(opts, history) @temporalio/worker Replay single Event History
Worker.runReplayHistories(opts, histories) @temporalio/worker Replay multiple Event Histories
workflowInterceptorModules @temporalio/testing Convert AssertionError to ApplicationFailure

Implementation Patterns

1. Test Server Setup (Jest)

Create one TestWorkflowEnvironment per test suite. Reuse it across tests.

import { TestWorkflowEnvironment } from '@temporalio/testing';
import { Worker } from '@temporalio/worker';
import { v4 as uuid4 } from 'uuid';

let testEnv: TestWorkflowEnvironment;

beforeAll(async () => {
  testEnv = await TestWorkflowEnvironment.createTimeSkipping();
});

afterAll(async () => {
  await testEnv?.teardown();
});

Jest config requirement: must set testEnvironment: 'node'. jsdom is NOT supported. Minimum Jest version: 27.0.0.

2. Integration Test with Worker

import { myWorkflow } from './workflows';

test('myWorkflow returns expected result', async () => {
  const taskQueue = 'test-' + uuid4();
  const worker = await Worker.create({
    connection: testEnv.nativeConnection,
    taskQueue,
    workflowsPath: require.resolve('./workflows'),
  });

  const result = await worker.runUntil(
    testEnv.client.workflow.execute(myWorkflow, {
      workflowId: uuid4(),
      taskQueue,
    }),
  );

  expect(result).toEqual('expected');
});

3. Mock Activities

Provide partial mock implementations to the Worker instead of real Activities.

import type * as activities from './activities';

const mockActivities: Partial<typeof activities> = {
  makeHTTPRequest: async () => '99',
};

const worker = await Worker.create({
  connection: testEnv.nativeConnection,
  taskQueue: 'test',
  workflowsPath: require.resolve('./workflows'),
  activities: mockActivities,
});

4. Test Activity in Isolation

import { MockActivityEnvironment } from '@temporalio/testing';

const env = new MockActivityEnvironment({ attempt: 2 });
const result = await env.run(myActivity, arg1, arg2);
expect(result).toBe(expectedValue);

Pass partial ActivityInfo fields to the constructor as needed (e.g., { attempt: 2 }).

5. Listen to Heartbeats

const env = new MockActivityEnvironment();
const heartbeats: unknown[] = [];

env.on('heartbeat', (detail: unknown) => {
  heartbeats.push(detail);
});

await env.run(myHeartbeatingActivity);
expect(heartbeats).toEqual([1, 2, 3]);

Note: MockActivityEnvironment does NOT throttle heartbeats (unlike a real Worker).

6. Test Activity Cancellation

import { CancelledFailure } from '@temporalio/activity';

const env = new MockActivityEnvironment();

env.on('heartbeat', () => {
  env.cancel();  // cancel after first heartbeat
});

await expect(env.run(myCancellableActivity)).rejects.toThrow(CancelledFailure);

7. Automatic Time Skipping

When using testEnv.client.workflow.execute() or .result(), the test server automatically fast-forwards through sleep() calls and condition() timeouts. Activities run in normal time.

// Workflow has: await sleep('1 day')
// This test completes in milliseconds, not 1 day
const result = await worker.runUntil(
  testEnv.client.workflow.execute(sleeperWorkflow, {
    workflowId: uuid4(),
    taskQueue: 'test',
  }),
);

8. Manual Time Skipping

Use .start() instead of .execute() to keep the server in normal time mode, then call testEnv.sleep() to advance time manually. Useful for testing intermediate states.

const handle = await testEnv.client.workflow.start(myWorkflow, {
  workflowId: uuid4(),
  taskQueue: 'test',
});

worker.run(); // don't await -- let it run in background

// Advance time and check intermediate state
await testEnv.sleep('25 hours');
const days = await handle.query(daysQuery);
expect(days).toBe(1);

9. Skip Time Inside Mock Activities

Call testEnv.sleep() from within a mock Activity to simulate long-running work while allowing the Workflow's timers to fire.

const mockActivities = {
  async processOrder() {
    await testEnv.sleep('2 days'); // Workflow timers fire during this
  },
  async sendNotificationEmail() {
    emailSent = true;
  },
};

10. Assert in Workflow Context

By default, a failed assert in Workflow code causes infinite Workflow Task retries. Use workflowInterceptorModules to convert AssertionError into ApplicationFailure that fails the Workflow Execution.

import { workflowInterceptorModules } from '@temporalio/testing';

const worker = await Worker.create({
  connection: testEnv.nativeConnection,
  interceptors: {
    workflowModules: workflowInterceptorModules,
  },
  workflowsPath: require.resolve('./workflows'),
});

11. Replay a Single Event History

import { Worker } from '@temporalio/worker';
import fs from 'fs';

const history = JSON.parse(await fs.promises.readFile('./history.json', 'utf8'));
await Worker.runReplayHistory(
  { workflowsPath: require.resolve('./workflows') },
  history,
);
// Throws DeterminismViolationError if code is incompatible

12. Replay in Bulk (CI Pipeline)

const executions = client.workflow.list({
  query: 'TaskQueue="my-queue" and StartTime > "2024-01-01T00:00:00"',
});
const histories = executions.intoHistories();
const results = Worker.runReplayHistories(
  { workflowsPath: require.resolve('./workflows') },
  histories,
);

for await (const result of results) {
  if (result.error) {
    console.error('Replay failed', result);
    process.exitCode = 1;
  }
}

13. Test Non-Workflow Functions in Workflow Context

Point workflowsPath at the file containing the function, then execute it as if it were a Workflow.

const worker = await Worker.create({
  connection: testEnv.nativeConnection,
  workflowsPath: require.resolve('./workflows/helper-functions'),
});

const result = await worker.runUntil(
  testEnv.client.workflow.execute(functionToTest, {
    workflowId: uuid4(),
    taskQueue: 'test',
  }),
);

If the function starts a Child Workflow, that child Workflow must be re-exported from the same file.

Common Mistakes

Mistake Fix
Using testEnvironment: 'jsdom' in Jest config Set testEnvironment: 'node'
Creating a new TestWorkflowEnvironment per test Create once in beforeAll, share across tests
Forgetting await testEnv.teardown() in afterAll Always teardown to release resources
Using worker.run() and then calling .execute() Use worker.runUntil(client.workflow.execute(...)) so the Worker shuts down after the Workflow completes
Calling .execute() or .result() when you want manual time control Use .start() instead, then call testEnv.sleep() to advance time
Failed assert in Workflow causing infinite Task retries Add workflowInterceptorModules to Worker interceptors
Mocking only some Activities but Worker expects all Use Partial<typeof activities> -- only mock what the test needs
Not re-exporting Child Workflows from test file Child Workflows must be exported from the same file as workflowsPath
Replaying with wrong workflowsPath Point to the file that exports the exact Workflow Definition matching the history
Using same taskQueue string across parallel tests Generate unique task queue names per test (e.g., 'test-' + uuid4())

Debugging Tips

  1. Test hangs indefinitely: Likely the Worker is not connected to the test server. Ensure you pass testEnv.nativeConnection to Worker.create().

  2. Time-skipping not working: Verify you called TestWorkflowEnvironment.createTimeSkipping(). If using manual time skip, make sure you did NOT call .execute() or .result() (those trigger automatic skip mode).

  3. DeterminismViolationError on replay: Your Workflow code changed in a non-backward-compatible way. Use Temporal versioning (patched / deprecatePatch) to handle differences.

  4. ReplayError (not DeterminismViolationError): Something other than non-determinism went wrong -- check that the history file is valid JSON and matches the expected format.

  5. Activity mock not being called: Confirm the taskQueue in the Worker matches the one in the Workflow execution options.

  6. MockActivityEnvironment heartbeat not throttled: This is by design. The mock does not throttle; real Workers do. Do not rely on heartbeat timing in mock tests.

  7. Time is global: Time skipping applies to ALL running tests in the same TestWorkflowEnvironment instance. Run tests that need different time behaviors in series or use separate environments.

Further Reading

If this skill does not answer your question, use Context7 to search /temporalio/sdk-typescript or /temporalio/samples-typescript for more details. Useful queries:

  • "How to set up TestWorkflowEnvironment for integration tests"
  • "MockActivityEnvironment usage examples"
  • "Workflow replay and determinism testing"
  • "Time skipping test patterns in TypeScript"
Install via CLI
npx skills add https://github.com/remodlai/skills-temporal --skill temporal-ts-testing-suite
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator