Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

NPM Version NPM Downloads GitHub License Discord YouTube Channel Views X (formerly Twitter) Follow

Dapr TestContainer for NodeJS

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.

Using the library

To use this library, add the dependency to your project:

npm install --save-dev @dapr/testcontainer-node

Configuring Dapr Runtime Version

By 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"));

Dapr PubSub Testing

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();

Dapr State Management Testing

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();

Dapr Binary Store Testing

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.

Dapr Workflow Testing

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();

Dapr Actor Testing

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();

Dapr Conversation Testing

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.

Dapr Cryptography Testing

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();

Dapr Secrets Testing

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();

Dapr Distributed Lock Testing

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" } });

Versions

This library follows Semantic Versioning.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages