THIS IS AN EARLY PREVIEW IN ACTIVE DEVELOPMENT
A comprehensive testing framework for Camunda process automation in Node.js/TypeScript, inspired by the Java camunda-process-test-java library.
Get started with a new Camunda process test project in seconds using the built-in scaffolding command:
# Install Camunda Process Test
npm install @camunda8/process-test --save-dev
# Generate configuration files
npx @camunda8/process-test config:init
This will:
camunda-test-config.json with sensible defaults# Preview what files would be created without writing them
npx @camunda8/process-test config:init --dry-run
# Force Jest configuration generation even if Jest isn't detected
npx @camunda8/process-test config:init --jest
# Get help for available options
npx @camunda8/process-test config:init --help
The scaffolding tool intelligently detects your project setup:
npm install @camunda8/process-test --save-dev
# Peer dependencies
npm install jest @types/jest --save-dev
import { Camunda8 } from '@camunda8/sdk';
import {
setupCamundaProcessTest,
CamundaAssert
} from '@camunda8/process-test';
describe('Order Process', () => {
/**
* setupCamundaProcessTest() should be called outside test blocks, and before any beforeAll or afterAll blocks in your test.
* It will install beforeAll, beforeEach, afterAll, and afterEach hooks to manage test state
* between tests, including setting up and recycling any containers.
*/
const setup = setupCamundaProcessTest();
test('should complete order process', async () => {
/**
* getContext() returns a CamundaTestContext object. This has methods for deploying resources
* and starting process instances that track the resources and dispose of them after each test.
* This is the best practice for test isolation.
*/
const context = setup.getContext();
// Deploy process
await context.deployResources(['./processes/order-process.bpmn']);
/**
* The Mock Job Worker will complete only one job. Awaiting this means that the test will continue only
* when a job has been completed.
*
* It is important to run tests using either `--runInBand` or `maxWorkers: 1`.
* Both worker mocks and external workers are managed by the framework, and after a test runs, all running
* workers are stopped. This will cause unpredictable behaviour if two tests are running at the same time.
*/
await context.mockJobWorker('collect-money')
.thenComplete({ paid: true });
await context.mockJobWorker('ship-parcel')
.thenComplete({ tracking: 'TR123456' });
// Start process instance
const processInstance = await context.createProcessInstance({
processDefinitionId: 'order-process',
variables: { orderId: 'order-123', amount: 99.99 }
});
// Verify process execution
const assertion = CamundaAssert.assertThat(processInstance);
await assertion.isCompleted();
await assertion.hasVariables({
paid: true,
tracking: 'TR123456'
});
}, 60000); // 60 second timeout for container startup
});
import { Camunda8 } from '@camunda8/sdk';
import {
CamundaProcessTest,
CamundaAssert,
CamundaProcessTestContext
} from '@camunda8/process-test';
@CamundaProcessTest
class MyProcessTest {
private client!: Camunda8; // Automatically injected
private context!: CamundaProcessTestContext; // Automatically injected
async testOrderProcess() {
// Deploy process
await this.context.deployResources(['./processes/order-process.bpmn']);
// Mock job workers
await this.context.mockJobWorker('collect-money')
.thenComplete({ paid: true });
await this.context.mockJobWorker('ship-parcel')
.thenComplete({ tracking: 'TR123456' });
// Start process instance
const processInstance = await this.context.createProcessInstance({
processDefinitionId: 'order-process',
variables: { orderId: 'order-123', amount: 99.99 }
});
// Verify process execution
const assertion = CamundaAssert.assertThat(processInstance);
await assertion.isCompleted();
await assertion.hasVariables({
paid: true,
tracking: 'TR123456'
});
}
}
Configure the testing framework via configuration files, environment variables, and Jest configuration.
The framework supports simple configuration discovery with two methods:
camunda-test-config.json at the project rootCAMUNDA_TEST_CONFIG_FILE to specify a custom config fileThe framework uses the following priority order:
camunda-test-config.json at project rootCreate a camunda-test-config.json file in your project root or test directory:
{
"camundaDockerImageName": "camunda/camunda",
"camundaDockerImageVersion": "8.8.0",
"connectorsDockerImageName": "camunda/connectors-bundle",
"connectorsDockerImageVersion": "8.8.0",
"runtimeMode": "MANAGED"
}
Project Structure with Configuration:
my-project/
โโโ camunda-test-config.json # Main configuration file
โโโ configs/ # Matrix testing configs
โ โโโ v8.8.0.json # Version-specific
โ โโโ v8.8.1.json
โ โโโ staging.json # Environment-specific
โ โโโ production.json
โโโ package.json
โโโ test/
โโโ shared-tests/ # Tests that run against all configs
โโโ common.test.ts
Matrix Testing Examples:
# Test against different configurations using environment variable
CAMUNDA_TEST_CONFIG_FILE=configs/v8.8.0.json npm test
CAMUNDA_TEST_CONFIG_FILE=configs/v8.8.1.json npm test
CAMUNDA_TEST_CONFIG_FILE=configs/staging.json npm test
| Property | Description | Default | Environment Variable |
|---|---|---|---|
camundaDockerImageName |
Zeebe container image name | camunda/camunda |
CAMUNDA_DOCKER_IMAGE_NAME |
camundaDockerImageVersion |
Zeebe container image version | 8.8.0 |
CAMUNDA_DOCKER_IMAGE_VERSION |
connectorsDockerImageName |
Connectors container image name | camunda/connectors-bundle |
CONNECTORS_DOCKER_IMAGE_NAME |
connectorsDockerImageVersion |
Connectors container image version | 8.8.0 |
CONNECTORS_DOCKER_IMAGE_VERSION |
runtimeMode |
Runtime mode (MANAGED or REMOTE) |
MANAGED |
CAMUNDA_RUNTIME_MODE |
zeebeClientId |
Client ID for OAuth authentication | "" |
ZEEBE_CLIENT_ID |
zeebeClientSecret |
Client secret for OAuth authentication | "" |
ZEEBE_CLIENT_SECRET |
camundaOauthUrl |
OAuth URL for authentication | "" |
CAMUNDA_OAUTH_URL |
zeebeRestAddress |
REST API address for remote Zeebe | "" |
ZEEBE_REST_ADDRESS |
zeebeGrpcAddress |
gRPC API address for remote Zeebe workers | Auto-derived from REST address | ZEEBE_GRPC_ADDRESS |
zeebeTokenAudience |
Token audience for OAuth | "" |
ZEEBE_TOKEN_AUDIENCE |
zeebeClientLogLevel |
gRPC client log level (NONE, ERROR, WARN, INFO, DEBUG) | NONE |
ZEEBE_CLIENT_LOG_LEVEL |
camundaAuthStrategy |
Authentication strategy | "" (auto-detect) |
CAMUNDA_AUTH_STRATEGY |
camundaMonitoringApiAddress |
Monitoring API address | Auto-calculated from REST address:9600 | CAMUNDA_MONITORING_API_ADDRESS |
connectorsRestApiAddress |
Connectors API address | Auto-calculated from REST address:8085 | CONNECTORS_REST_API_ADDRESS |
flushProcesses |
Cancel all active process instances on startup (REMOTE mode only) | false |
CAMUNDA_FLUSH_PROCESSES |
testScope |
Test organization hint | "" |
- |
description |
Human-readable description | "" |
- |
Project Root Discovery:
The framework finds the project root by searching up the directory tree for package.json, then looks for camunda-test-config.json in that directory.
Environment Variable Override:
Set CAMUNDA_TEST_CONFIG_FILE to the path of any configuration file (relative to project root or absolute path). This completely bypasses the default project root discovery.
# Relative to project root
CAMUNDA_TEST_CONFIG_FILE=configs/staging.json npm test
# Absolute path
CAMUNDA_TEST_CONFIG_FILE=/path/to/config.json npm test
Production-ready setup:
{
"camundaDockerImageName": "camunda/camunda",
"camundaDockerImageVersion": "8.8.0-alpha6",
"connectorsDockerImageName": "camunda/connectors-bundle",
"connectorsDockerImageVersion": "8.8.0-alpha6",
"runtimeMode": "MANAGED"
}
Development with specific versions:
{
"camundaDockerImageName": "camunda/camunda",
"camundaDockerImageVersion": "8.8.0",
"runtimeMode": "MANAGED"
}
C8Run example (auto-calculated APIs):
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://localhost:8080",
"zeebeGrpcAddress": "grpc://localhost:26500",
"camundaAuthStrategy": "NONE"
}
Remote runtime (existing Camunda instance):
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://your-cluster.region.zeebe.camunda.io:443",
"zeebeGrpcAddress": "grpcs://your-cluster.region.zeebe.camunda.io:443",
"zeebeClientId": "your-client-id",
"zeebeClientSecret": "your-client-secret",
"camundaOauthUrl": "https://login.cloud.camunda.io/oauth/token",
"zeebeTokenAudience": "zeebe.camunda.io"
}
SaaS example with explicit API addresses:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://your-cluster.region.zeebe.camunda.io:443",
"zeebeGrpcAddress": "grpcs://your-cluster.region.zeebe.camunda.io:443",
"zeebeClientId": "your-client-id",
"zeebeClientSecret": "your-client-secret",
"camundaOauthUrl": "https://login.cloud.camunda.io/oauth/token",
"zeebeTokenAudience": "zeebe.camunda.io",
"camundaMonitoringApiAddress": "https://your-cluster.region.zeebe.camunda.io:9600",
"connectorsRestApiAddress": "https://your-cluster.region.zeebe.camunda.io:8085"
}
Self-managed example with custom ports:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://camunda.mycompany.com:8080",
"zeebeGrpcAddress": "grpc://camunda.mycompany.com:26500",
"camundaAuthStrategy": "NONE",
"camundaMonitoringApiAddress": "http://camunda.mycompany.com:9600",
"connectorsRestApiAddress": "http://connectors.mycompany.com:8085"
}
Test environment with process cleanup:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://test-camunda:8080",
"camundaAuthStrategy": "NONE",
"flushProcesses": true
}
Override configuration file settings or set additional options:
# Environment configuration
CAMUNDA_DOCKER_IMAGE_VERSION=8.8.0-alpha6
CAMUNDA_DOCKER_IMAGE_NAME=camunda/camunda
CONNECTORS_DOCKER_IMAGE_VERSION=8.8.0-alpha6
CAMUNDA_RUNTIME_MODE=MANAGED
# Remote runtime configuration (C8Run)
ZEEBE_REST_ADDRESS=http://localhost:8080
CAMUNDA_RUNTIME_MODE=REMOTE
# Remote runtime configuration (SaaS)
ZEEBE_REST_ADDRESS=https://your-cluster.region.zeebe.camunda.io:443
ZEEBE_GRPC_ADDRESS=grpcs://your-cluster.region.zeebe.camunda.io:443 # Secure gRPC
ZEEBE_CLIENT_ID=your-client-id
ZEEBE_CLIENT_SECRET=your-client-secret
CAMUNDA_OAUTH_URL=https://login.cloud.camunda.io/oauth/token
ZEEBE_TOKEN_AUDIENCE=zeebe.camunda.io
CAMUNDA_AUTH_STRATEGY=OAUTH
CAMUNDA_RUNTIME_MODE=REMOTE
# Remote runtime configuration (C8Run - insecure)
ZEEBE_REST_ADDRESS=http://localhost:8080
ZEEBE_GRPC_ADDRESS=grpc://localhost:26500 # Insecure gRPC for local development
CAMUNDA_AUTH_STRATEGY=NONE
CAMUNDA_RUNTIME_MODE=REMOTE
# Runtime configuration
CAMUNDA_CONNECTORS_ENABLED=true
# Process management (REMOTE mode only)
CAMUNDA_FLUSH_PROCESSES=true # Cancel all active process instances on startup (use with caution)
# Debug settings
DEBUG=camunda:* # Enable debug logging
# Matrix testing override
CAMUNDA_TEST_CONFIG_FILE=configs/staging.json # Use specific config file
The framework supports powerful matrix testing capabilities for CI/CD pipelines using the CAMUNDA_TEST_CONFIG_FILE environment variable to specify different configuration files.
name: Matrix Testing
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
config:
- configs/v8.8.0.json
- configs/v8.8.1.json
- configs/v8.9.0.json
- configs/staging.json
- configs/production.json
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests with specific config
env:
CAMUNDA_TEST_CONFIG_FILE: ${{ matrix.config }}
run: npm test
configs/v8.8.0.json - Version-specific testing
{
"description": "Camunda 8.8.0 testing",
"camundaDockerImageVersion": "8.8.0",
"connectorsDockerImageVersion": "8.8.0",
"runtimeMode": "MANAGED"
}
configs/staging.json - Environment-specific testing
{
"description": "Staging environment testing",
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://staging.company.com:8080",
"zeebeGrpcAddress": "grpcs://staging.company.com:26500",
"camundaAuthStrategy": "NONE"
}
configs/production.json - Production validation
{
"description": "Production environment validation",
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://prod.company.com:8080",
"zeebeGrpcAddress": "grpcs://prod.company.com:26500",
"zeebeClientId": "${PROD_CLIENT_ID}",
"zeebeClientSecret": "${PROD_CLIENT_SECRET}",
"camundaOauthUrl": "https://login.cloud.camunda.io/oauth/token"
}
# Test against different configurations locally
for config in configs/*.json; do
echo "Testing with $config"
CAMUNDA_TEST_CONFIG_FILE="$config" npm test
done
# Test specific combinations
CAMUNDA_TEST_CONFIG_FILE=configs/v8.8.0.json npm run test:integration
CAMUNDA_TEST_CONFIG_FILE=configs/staging.json npm run test:e2e
Jest configuration in jest.config.js:
module.exports = {
preset: 'ts-jest', // If you are testing TypeScript
testEnvironment: 'node',
// Global timeout - will be overridden per test as needed
testTimeout: process.env.CI ? 300000 : 30000,
detectOpenHandles: true,
forceExit: true,
maxWorkers: 1, // Run tests sequentially to avoid container conflicts
};
Essential is to set the test timeout high enough to pull and start the container if you are running in MANAGED mode.
The framework supports connecting to remote Camunda instances instead of managing local Docker containers. This is useful for testing against:
For local or internal instances without authentication:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://localhost:8080",
"zeebeGrpcAddress": "grpc://localhost:26500",
"camundaAuthStrategy": "NONE"
}
Required for Camunda SaaS instances:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://your-cluster.region.zeebe.camunda.io:443",
"zeebeGrpcAddress": "grpcs://your-cluster.region.zeebe.camunda.io:26500",
"zeebeClientId": "your-client-id",
"zeebeClientSecret": "your-client-secret",
"camundaOauthUrl": "https://login.cloud.camunda.io/oauth/token",
"zeebeTokenAudience": "zeebe.camunda.io"
}
The framework automatically calculates monitoring and connectors API addresses:
For zeebeRestAddress: "https://example.com:443":
https://example.com:9600https://example.com:8085You can override these defaults by explicitly setting:
camundaMonitoringApiAddressconnectorsRestApiAddressWhen testing against a REMOTE engine (such as C8Run), you may want to clean up any active process instances from previous test runs to ensure test isolation. The framework provides the flushProcesses option:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://localhost:8080",
"flushProcesses": true
}
Environment Variable:
CAMUNDA_FLUSH_PROCESSES=true
โ ๏ธ Important Safety Notes:
false - Must be explicitly enabledWhen to Use:
Example Usage:
// Configuration file approach
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "http://test-camunda:8080",
"flushProcesses": true // Cleanup before each test run
}
// Environment variable approach
CAMUNDA_FLUSH_PROCESSES=true npm test
Use deployResources() for deploying resources with optional automatic cleanup support:
// Deploy single resource
await context.deployResources(['./processes/my-process.bpmn']);
// Deploy multiple resources at once
await context.deployResources([
'./processes/order-process.bpmn',
'./decisions/approval.dmn',
'./forms/order-form.form'
]);
// Deploy with automatic cleanup
// Resources will be automatically deleted after the test completes
await context.deployResources(
['./processes/my-process.bpmn'],
{ autoDelete: true }
);
Auto-Delete Feature: When autoDelete: true is specified, deployed resources are automatically tracked and deleted after each test.
Using the framework's context object to create process instances means that any active process instance created in a test is cancelled in the afterEach lifecycle, ensuring test isolation.
const camunda = client.getCamundaRestClient();
const processInstance = await context.createProcessInstance({
processDefinitionId: 'my-process',
variables: { input: 'test-data' }
});
The framework operates in two modes with different cleanup behaviors:
autoDelete: true for automatic resource cleanup// Auto-cleanup example
await context.deployResources(
['./processes/test-process.bpmn'],
{ autoDelete: true } // Automatically deleted after this test
);
Best Practices:
autoDelete: true when testing against REMOTE environmentsNote that each Job Worker Mock will only process one job. If you want to complete more than one job, then you should create a real job worker in the test, using the injected client.
// Complete successfully
await context.mockJobWorker('payment-service')
.thenComplete({ transactionId: 'tx-123' });
// Throw business error
await context.mockJobWorker('validation-service')
.thenThrowBpmnError('VALIDATION_ERROR', 'Invalid data');
// Throw technical error
await context.mockJobWorker('external-api')
.thenThrowError('Connection timeout');
// Custom behavior
await context.mockJobWorker('complex-task')
.withHandler(async (job) => {
const { amount } = job.variables;
if (amount > 1000) {
return { approved: false };
}
return { approved: true };
});
For testing external workers, use the framework's client:
test('should process jobs with external gRPC worker', async () => {
/**
* getClient() returns a managed instance of the Camunda8 class configured for connection to
* the test engine. This client is also managed to stop any polling workers when the test ends.
* This means that you do not need to manually manage closing workers in your tests.
*/
const client = setup.getClient();
const context = setup.getContext();
// Deploy process with service task
await context.deployResources(['./processes/worker-process.bpmn']);
// Create external gRPC worker - closing the worker on test completion is automatically managed by framework
const grpcClient = client.getZeebeGrpcApiClient();
grpcClient.createWorker({
taskType: 'external-task',
taskHandler: (job) => {
// Process the job
const { input } = job.variables;
return job.complete({
output: `Processed: ${input}`,
timestamp: new Date().toISOString()
});
}
});
// Start process instance
const processInstance = await context.createProcessInstance({
processDefinitionId: 'worker-process',
variables: { input: 'test-data' }
});
// Verify process completion
const assertion = CamundaAssert.assertThat(processInstance);
await assertion.isCompleted();
await assertion.hasVariables({
output: 'Processed: test-data'
});
// Worker is automatically closed by the framework during test cleanup
});
The framework automatically manages the lifecycle of both gRPC and REST job workers:
Automatic Registration & Cleanup:
client.getZeebeGrpcApiClient().createWorker() are automatically stopped after a testclient.getCamundaRestClient().createJobWorker() are automatically stopped after a testworker.close() or worker.stop() calls--runInBand option or maxWorkers: 1 configuration option. All workers are stopped at the end of a test.Worker testing pattern without Lifecycle Management:
const worker = grpcClient.createWorker(config);
try {
// test logic
} finally {
worker.close(); // Manual cleanup required
}
Tests with our Automatic Lifecycle Management:
grpcClient.createWorker(config);
// test logic - worker automatically cleaned up by framework!
For REMOTE mode with external Zeebe clusters:
{
"runtimeMode": "REMOTE",
"zeebeRestAddress": "https://cluster.region.zeebe.camunda.io:443",
"zeebeGrpcAddress": "grpcs://cluster.region.zeebe.camunda.io:443",
"zeebeClientId": "your-client-id",
"zeebeClientSecret": "your-client-secret"
}
Or use environment variables:
ZEEBE_REST_ADDRESS=https://cluster.region.zeebe.camunda.io:443
ZEEBE_GRPC_ADDRESS=grpcs://cluster.region.zeebe.camunda.io:443 # Note: protocol-based TLS
ZEEBE_CLIENT_ID=your-client-id
ZEEBE_CLIENT_SECRET=your-client-secret
Protocol-based TLS Detection:
grpc:// - Insecure connection (local development, C8Run)grpcs:// - Secure TLS connection (SaaS, production)The framework automatically configures TLS based on the protocol in ZEEBE_GRPC_ADDRESS.
gRPC Client Logging: By default, the gRPC client logging is suppressed to reduce noise. You can enable it for debugging:
# Enable detailed logging
ZEEBE_CLIENT_LOG_LEVEL=INFO npm test
# Available levels: NONE (default), ERROR, WARN, INFO, DEBUG
ZEEBE_CLIENT_LOG_LEVEL=DEBUG npm test
Or in configuration file:
{
"zeebeClientLogLevel": "INFO"
}
const assertion = CamundaAssert.assertThat(processInstance);
// Basic state assertions
await assertion.isCompleted(); // Process finished successfully
await assertion.isActive(); // Process is still running
await assertion.isTerminated(); // Process was terminated
// Variable assertions
await assertion.hasVariables({ status: 'approved' });
await assertion.hasVariable('orderStatus', 'completed');
// Activity assertions
await assertion.hasCompletedElements('task1', 'task2');
await assertion.hasActiveElements('waiting-task');
// Error assertions
await assertion.hasNoIncidents();
await assertion.hasIncidentWithMessage('timeout');
const userTaskAssertion = CamundaAssert.assertThatUserTask({
type: 'elementId',
value: 'approve-task'
});
await userTaskAssertion.exists();
await userTaskAssertion.isAssignedTo('john.doe');
await userTaskAssertion.isUnassigned();
await userTaskAssertion.hasCandidateGroups('managers');
await userTaskAssertion.hasVariables({ priority: 'high' });
await userTaskAssertion.complete({ approved: true });
const decisionAssertion = CamundaAssert.assertThatDecision({
type: 'decisionId',
value: 'approval'
});
await decisionAssertion.wasEvaluated();
await decisionAssertion.hasResult({ approved: true });
await decisionAssertion.hasResultContaining({ score: 85 });
โ ๏ธ IMPORTANT WARNING: Time manipulation may fail in REMOTE mode (SaaS/C8Run environments). For reliable timer testing, use MANAGED mode with Docker containers. SaaS and C8Run typically reject clock modification attempts even with
ZEEBE_CLOCK_CONTROLLED=true.
Control Zeebe's internal clock for testing time-based processes like timers, timeouts, and scheduled tasks.
// Increase time for timer testing (async - advances Zeebe's actual clock)
await context.increaseTime({ hours: 24 });
await context.increaseTime({ minutes: 30 });
await context.increaseTime(5000); // milliseconds
// Get current test time
const currentTime = context.getCurrentTime();
test('should complete timer-based process', async () => {
const context = setup.getContext();
// Deploy process with timer event
await context.deployResources(['./processes/timer-process.bpmn']);
// Mock service task after timer
await context.mockJobWorker('after-timer')
.thenComplete({ timerCompleted: true });
// Start process
const processInstance = await context.createProcessInstance({
processDefinitionId: 'timer-process',
variables: {}
});
// Verify process is waiting at timer
const assertion1 = CamundaAssert.assertThat(processInstance);
await assertion1.isActive();
await assertion1.hasActiveElements('wait-timer');
// Advance time to trigger timer (advances Zeebe's internal clock)
await context.increaseTime({ hours: 1 });
// Verify timer triggered and process completed
const assertion2 = CamundaAssert.assertThat(processInstance);
await assertion2.isCompleted();
await assertion2.hasCompletedElements('wait-timer', 'after-timer-task');
});
The framework provides direct access to Zeebe's clock management through the CamundaClock utility:
import { CamundaClock } from '@camunda8/process-test';
// Create clock instance (usually done automatically by the framework)
const runtime = setup.getRuntime();
const clock = new CamundaClock(runtime);
// Get current Zeebe time
const currentTime = await clock.getCurrentTime();
// Advance clock by milliseconds
await clock.addTime(60000); // 1 minute
// Advance using convenience method
await clock.advanceTime(1, 'hours');
await clock.advanceTime(30, 'minutes');
await clock.advanceTime(5, 'seconds');
// Reset clock to system time
await clock.resetClock();
// All these are equivalent to 1 hour
await context.increaseTime(3600000); // milliseconds
await context.increaseTime({ hours: 1 });
await context.increaseTime({ minutes: 60 });
await context.increaseTime({ seconds: 3600 });
// Combined durations
await context.increaseTime({
days: 1,
hours: 2,
minutes: 30,
seconds: 45
});
// Timer tests: Use MANAGED mode (Docker containers)
describe('Timer-based processes', () => {
// Configuration: camunda-test-config.json
// { "runtimeMode": "MANAGED" }
test('should handle 24-hour timer', async () => {
// Deploy and start process with timer
await context.deployProcess('./timer-process.bpmn');
// This works reliably in MANAGED mode
await context.increaseTime({ hours: 24 });
// Verify timer completion...
});
});
// Non-timer tests: Can use REMOTE mode
describe('Business logic processes', () => {
// Configuration: camunda-test-config.json
// { "runtimeMode": "REMOTE", "zeebeRestAddress": "..." }
test('should complete approval workflow', async () => {
// No time manipulation needed - works great in REMOTE mode
// Test business logic, user tasks, service tasks, etc.
});
});
Notes:
ZEEBE_CLOCK_CONTROLLED=true is automatically set.ZEEBE_CLOCK_CONTROLLED=true.Enable comprehensive debugging to inspect Docker operations, process deployments, and test execution:
# Enable all debugging
DEBUG=camunda:* npm test
# Enable specific categories
DEBUG=camunda:test:container npm test # Docker operations
DEBUG=camunda:test:deploy npm test # Process deployments
DEBUG=camunda:test:runtime npm test # Runtime lifecycle
DEBUG=camunda:test:logs npm test # Container log capture
camunda:test:runtime - Runtime startup/shutdowncamunda:test:container - Docker container operationscamunda:test:docker - Docker image pulls and versionscamunda:test:deploy - BPMN/DMN deploymentscamunda:test:worker - Job worker operationscamunda:test:context - Test context lifecyclecamunda:test:logs - Container log captureWhen debugging is enabled, detailed container logs are saved to ./camunda-test-logs/:
elasticsearch-{timestamp}.log - Elasticsearch startup and operation logscamunda-{timestamp}.log - Camunda broker logs with BPMN processing detailsconnectors-{timestamp}.log - Connector runtime logs (if enabled)For detailed debugging instructions, see DEBUG.md.
This repository includes working examples:
examples/simple.test.ts - Basic process testingexamples/grpc-worker.test.ts - gRPC worker testing with external workersexamples/debug.test.ts - Debug mode demonstrationexamples/basic-test.test.ts - Comprehensive examplesAll examples include BPMN/DMN files in examples/resources/.
# Build first
npm run build
# Run simple example
npm test examples/simple.test.ts
# Run gRPC worker example
npm test examples/grpc-worker.test.ts
# Run with debugging
DEBUG=camunda:* npm test examples/debug.test.ts
# Run all examples
npm test examples/
When testing against shared environments (SaaS, C8Run, etc.), use auto-cleanup to prevent resource accumulation:
describe('Payment Process Tests', () => {
const setup = setupCamundaProcessTest();
test('should handle successful payment in REMOTE environment', async () => {
const context = setup.getContext();
// Deploy with automatic cleanup
await context.deployResources([
'./processes/payment-process.bpmn',
'./decisions/credit-check.dmn'
], {
autoDelete: true // Resources deleted automatically after test
});
// Test logic here...
// Resources will be automatically cleaned up in afterEach()
});
test('should handle multiple resource types', async () => {
const context = setup.getContext();
// Check runtime mode for conditional cleanup
const mode = context.getRuntimeMode();
const shouldCleanup = mode === 'REMOTE';
await context.deployResources([
'./processes/order-process.bpmn',
'./forms/customer-form.form',
'./decisions/approval.dmn'
], {
autoDelete: shouldCleanup
});
// Test implementation...
});
});
describe('Order Integration Test', () => {
const setup = setupCamundaProcessTest();
test('should complete order flow', async () => {
const client = setup.getClient();
const context = setup.getContext();
// Deploy all related processes and decisions
await context.deployProcess('./processes/order-process.bpmn');
await context.deployProcess('./processes/payment-process.bpmn');
await context.deployDecision('./decisions/credit-check.dmn');
// Mock external services
await context.mockJobWorker('credit-check-service')
.thenComplete({ creditScore: 750 });
await context.mockJobWorker('payment-gateway')
.thenComplete({ transactionId: 'tx-12345', status: 'success' });
// Test the complete flow
const camunda = client.getCamundaRestClient();
const orderInstance = await camunda.createProcessInstance({
processDefinitionId: 'order-process',
variables: { customerId: 'c123', amount: 599.99 }
});
const assertion = CamundaAssert.assertThat(orderInstance);
await assertion.isCompleted();
await assertion.hasVariables({
creditScore: 750,
paymentStatus: 'success',
orderStatus: 'completed'
});
});
});
test('should handle payment failure', async () => {
const client = setup.getClient();
const context = setup.getContext();
await context.deployProcess('./processes/payment-process.bpmn');
// Simulate payment failure
await context.mockJobWorker('payment-service')
.thenThrowBpmnError('PAYMENT_FAILED', 'Insufficient funds');
const camunda = client.getCamundaRestClient();
const processInstance = await camunda.createProcessInstance({
processDefinitionId: 'payment-process',
variables: { amount: 1000000 } // Large amount
});
// Verify error handling path
const assertion = CamundaAssert.assertThat(processInstance);
await assertion.isCompleted();
await assertion.hasCompletedElements('payment-failed-event', 'notify-customer');
});
maxWorkers: 1 in Jest config# Pre-pull images to speed up tests
docker pull camunda/camunda:8.8.0-alpha6
# Clean up containers after testing
docker container prune -f
Solution: Start Docker Desktop
# Check Docker is running
docker ps
# If not running, start Docker Desktop
Solution: Increase Jest timeout or check Docker resources
# Run with longer timeout
npm test --testTimeout=300000
# Check Docker resources in Docker Desktop settings
Solution: Clean up existing containers
# Stop all Camunda containers
docker stop $(docker ps -q --filter ancestor=camunda/camunda)
# Or restart Docker Desktop
# See container startup details
DEBUG=camunda:test:container npm test
# Check deployment issues
DEBUG=camunda:test:deploy npm test
# Monitor runtime problems
DEBUG=camunda:test:runtime npm test
# Debug worker lifecycle management
DEBUG=camunda:test:worker npm test
# Monitor test cleanup operations
DEBUG=camunda:test:cleanup npm test
# Capture container logs for detailed inspection
DEBUG=camunda:test:logs npm test
# Then check ./camunda-test-logs/ for detailed container logs
๐ Complete API Documentation - Comprehensive TypeDoc-generated API documentation
setupCamundaProcessTest(): Function for test setup@CamundaProcessTest: Decorator for test classesCamundaAssert: Main assertion entry pointCamundaProcessTestContext: Test context and utilitiesCamundaClock: Clock management for time-based testingJobWorkerMock: Job worker mocking utilitiesdeployResources(paths, options?): Deploy multiple resources with optional auto-cleanup
paths: Array of file paths to BPMN, DMN, or Form filesoptions.autoDelete: Boolean - automatically delete resources after test (REMOTE mode only)deployProcess(path, processId?): Deploy single BPMN process (deprecated)deployDecision(path): Deploy single DMN decision (deprecated)createProcessInstance(request): Create and start process instance with automatic tracking
createProcessInstanceWithResult(request): Create process instance and await completion
getRuntimeMode(): Get current runtime mode ('MANAGED' | 'REMOTE')getClient(): Get Camunda 8 client instancegetGatewayAddress(): Get Zeebe gateway addressgetConnectorsAddress(): Get connectors runtime addressmockJobWorker(jobType): Create a mock job worker for the specified job typeclient.getZeebeGrpcApiClient().createWorker() are automatically registered and closedclient.getCamundaRestClient().createJobWorker() are automatically registered and stoppedworker.close() callsDEBUG=camunda:test:worker to monitor worker registration and cleanupincreaseTime(duration): Advance Zeebe's internal clockgetCurrentTime(): Get current test timeresetTime(): Reset time to system timewaitUntil(condition, options?): Wait for a condition with pollingresetTestState(): Reset test state between test methodscleanupTestData(): Clean up test data after test methodsThe framework supports two runtime modes and provides a way to detect which mode is active for differential test behavior:
// Function approach
const setup = setupCamundaProcessTest();
const context = setup.getContext();
const runtimeMode = context.getRuntimeMode(); // Returns 'MANAGED' | 'REMOTE'
// Decorator approach
@CamundaProcessTest
class MyTest {
private context!: CamundaProcessTestContext;
async testWithRuntimeSpecificBehavior() {
const runtimeMode = this.context.getRuntimeMode();
if (runtimeMode === 'MANAGED') {
// Test behavior specific to Docker container environment
// - Can test container logs
// - Can test container restart scenarios
// - Full control over Camunda lifecycle
} else {
// Test behavior specific to remote Camunda instance
// - Cannot control container lifecycle
// - May have different performance characteristics
// - Need to handle external dependencies
}
}
}
Runtime Modes:
MANAGED (default): Uses Docker containers managed by TestContainers
REMOTE: Connects to external Camunda instance (SaaS, C8Run, etc.)
ProcessInstanceAssert: Process instance assertionsUserTaskAssert: User task assertionsDecisionInstanceAssert: Decision instance assertions{ type: 'id' | 'name' | 'type' | 'custom', value: string | function }{ type: 'key' | 'processId' | 'custom', value: string | function }{ type: 'key' | 'elementId' | 'assignee' | 'custom', value: string | function }{ type: 'key' | 'decisionId' | 'processInstanceKey' | 'custom', value: string | function }We welcome contributions! Please see our Contributing Guide for details.
This library includes comprehensive CI/CD capabilities with sophisticated GitHub Actions workflows:
npm test)npm run test:integration)# Unit tests (fast, no Docker required)
npm test
# Integration tests (requires Docker)
npm run test:integration
# Run specific integration test
npm run test:integration -- --testNamePattern="simple"
Apache License 2.0 - see LICENSE file for details.