Dapr is a CNCF and open-source project that enables developers with consistent application-level APIs to develop secure, scalable, and resilient cloud-native applications.
The Testcontainers Dapr module for NodeJS enables local development and testing of Dapr-enabled applications by providing a DaprContainer that sets up a Dapr sidecar instance. This container provides an in-memory implementation of Dapr APIs by default, facilitating testing without requiring a full Dapr installation or external dependencies.
Usage examples can be found in src/DaprContainer.test.ts, src/ActorHarness.test.ts, src/ConversationHarness.test.ts, src/CryptographyHarness.test.ts, src/PubSubHarness.test.ts, src/SecretStoreHarness.test.ts, src/StateManagementHarness.test.ts, and src/WorkflowHarness.test.ts.
To use this library, add the dependency to your project:
npm install --save-dev @dapr/testcontainer-nodeBy default, the container uses Dapr version 1.18.4. You can override the Dapr runtime version globally for e2e testing using the DAPR_RUNTIME_VERSION environment variable:
export DAPR_RUNTIME_VERSION="1.18.4"You can also specify a custom image when instantiating containers:
import { DaprContainer, getDaprRuntimeImage } from "@dapr/testcontainer-node";
const dapr = new DaprContainer(getDaprRuntimeImage("1.18.4"));You can use PubSubHarness or .withPubSub() on DaprContainer to test Dapr PubSub with an automated RabbitMQ message broker:
import { PubSubHarness } from "@dapr/testcontainer-node";
const harness = new PubSubHarness({
appPort: 8080,
});
await harness.start();
const client = harness.createDaprClient();
await client.pubsub.publish(harness.getPubSubName(), "my-topic", { message: "Hello World" });
await harness.stop();You can use StateManagementHarness or .withStateManagement() on DaprContainer to test Dapr State Management backed by the Redis container:
import { StateManagementHarness } from "@dapr/testcontainer-node";
const harness = new StateManagementHarness();
await harness.start();
const client = harness.createDaprClient();
await client.start();
const storeName = harness.getStateStoreName();
// Save and retrieve state
await client.state.save(storeName, [{ key: "my-key", value: { name: "Alice" } }]);
const state = await client.state.get(storeName, "my-key");
await harness.stop();You can use WorkflowHarness or .withWorkflow() on DaprContainer to test Dapr Workflows with an automated Redis actor state store, placement service, and scheduler service:
import { WorkflowHarness } from "@dapr/testcontainer-node";
import { WorkflowRuntime, DaprWorkflowClient } from "@dapr/dapr";
const harness = new WorkflowHarness();
await harness.start();
const runtime = harness.createWorkflowRuntime();
runtime.registerWorkflow(myWorkflow);
runtime.registerActivity(myActivity);
await runtime.start();
const client = harness.createWorkflowClient();
const instanceId = await client.scheduleNewWorkflow(myWorkflow, "input");
const state = await client.waitForWorkflowCompletion(instanceId);
await harness.stop();You can use ActorHarness or .withActors() on DaprContainer to test Dapr Actors backed by the Redis actor state store, placement service, and scheduler service:
import { ActorHarness } from "@dapr/testcontainer-node";
import { AbstractActor, ActorId } from "@dapr/dapr";
import { TestContainers } from "testcontainers";
interface ICounterActor {
increment(amount: number): Promise<number>;
getCount(): Promise<number>;
}
class CounterActor extends AbstractActor implements ICounterActor {
async increment(amount = 1): Promise<number> {
const stateManager = this.getStateManager<number>();
const [hasValue, current] = await stateManager.tryGetState("counter");
const count = hasValue && current !== null && current !== undefined ? current : 0;
const next = count + amount;
await stateManager.setState("counter", next);
await stateManager.saveState();
return next;
}
async getCount(): Promise<number> {
const stateManager = this.getStateManager<number>();
const [hasValue, count] = await stateManager.tryGetState("counter");
return hasValue && count !== null && count !== undefined ? count : 0;
}
}
const appPort = 8090;
await TestContainers.exposeHostPorts(appPort);
const harness = new ActorHarness({
appPort,
appChannelAddress: "host.testcontainers.internal",
});
// Register actor on server
const server = harness.createDaprServer({
serverPort: appPort.toString(),
serverHost: "0.0.0.0",
});
await server.actor.registerActor(CounterActor);
await server.actor.init();
await server.daprServer.start("0.0.0.0", appPort.toString());
// Start harness (starts placement, scheduler, redis, and daprd sidecar)
await harness.start();
// Create actor proxy and invoke methods
const proxy = harness.createActorProxy<ICounterActor>(CounterActor, "counter-1");
const count = await proxy.increment(5); // 5
await harness.stop();ConversationHarness starts Dapr and a CPU-only Ollama container, pulls the small smollm2:135m model by default, and configures a conversation.ollama component:
import { ConversationHarness } from "@dapr/testcontainer-node";
await using harness = await new ConversationHarness().start();
const response = await harness.converse("Reply with the word pong.");
console.log(response.content, response.contextId);Use modelName, ollamaContainer, or ollamaEndpoint in the harness options to override the defaults.
CryptographyHarness configures the crypto.dapr.localstorage component and copies a local directory containing PEM, JWK, or raw symmetric keys into the Dapr sidecar:
import { CryptographyHarness } from "@dapr/testcontainer-node";
const harness = new CryptographyHarness({
keyPath: "./keys",
});
await harness.start();
const client = harness.createDaprClient();
const encrypted = await client.crypto.encrypt("secret data", {
componentName: harness.getComponentName(),
keyName: "rsa-key.pem",
keyWrapAlgorithm: "RSA-OAEP-256",
});
const decrypted = await client.crypto.decrypt(encrypted, {
componentName: harness.getComponentName(),
});
await harness.stop();You can use SecretStoreHarness or .withSecretStore() on DaprContainer to test the Dapr Secrets building block using a
local file secret store (secretstores.local.file). The harness writes both the component YAML and its backing JSON
secrets file into the container:
import { SecretStoreHarness } from "@dapr/testcontainer-node";
const harness = new SecretStoreHarness({
secrets: {
secret1: "value1",
connection: { username: "admin", password: "s3cr3t" },
},
});
await harness.start();
await harness.getSecretValue("secret1"); // "value1"
await harness.getSecretValue("connection:username"); // "admin" (nested keys are flattened)
await harness.getBulkSecrets();
await harness.stop();You can use DistributedLockHarness or .withDistributedLock() on DaprContainer to test Dapr Distributed Locks with an automated Redis lock store:
import { DistributedLockHarness } from "@dapr/testcontainer-node";
import { LockStatus } from "@dapr/dapr";
const harness = new DistributedLockHarness();
await harness.start();
const client = harness.createDaprClient();
await client.start();
const lockResponse = await client.lock.lock(
DistributedLockHarness.DistributedLockComponentName,
"resource-id",
"owner-id",
10
);
console.log(lockResponse.success);
const unlockResponse = await client.lock.unlock(
DistributedLockHarness.DistributedLockComponentName,
"resource-id",
"owner-id"
);
console.log(unlockResponse.status === LockStatus.Success);
await harness.stop();Secrets can also be loaded from an existing JSON file on the host, and the nested separator is configurable:
const harness = new SecretStoreHarness({
secretStoreName: "my-secrets",
secretsFilePath: "./test/secrets.json",
nestedSeparator: ".",
});To register one or more secret stores directly on a DaprContainer:
import { DaprContainer } from "@dapr/testcontainer-node";
const dapr = new DaprContainer()
.withNetwork(network)
.withSecretStore({ secrets: { alpha: "one" } })
.withSecretStore({ name: "second-store", secrets: { beta: "two" } });This library follows Semantic Versioning.