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();BinaryStoreHarness or .withBinaryStore() on DaprContainer configures a binarystore.in-memory component.
The Binary Store API requires Dapr 1.19 or a supporting prerelease (such as 1.19.0-rc.4); the library's default
1.18.4 runtime does not support it. Set DAPR_RUNTIME_VERSION to a supporting version or pass daprRuntimeImage:
import { BinaryStoreHarness } from "@dapr/testcontainer-node";
await using harness = await new BinaryStoreHarness({
daprRuntimeImage: "daprio/daprd:1.19.0-rc.4",
binaryStoreName: "files",
}).start();
await harness.setBinaryFile("example.bin", Buffer.from([0, 128, 255]));
const data = await harness.getBinaryFile("example.bin"); // Buffer
await harness.deleteBinaryFile("example.bin");The helpers use the v1.0-alpha1 HTTP API because the Node SDK does not yet expose Binary Store operations.
setBinaryFile overwrites existing files by default; pass false as its third argument to reject duplicates.
Missing files and failed requests throw errors containing the HTTP status and response body.
Optional prefix, maxSize, and maxTotalSize settings are passed to the in-memory component.
The store needs no external storage service; data is local to the sidecar and is lost when it stops.
See src/BinaryStoreHarness.test.ts for configuration and integration examples.
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.