Skip to main content

DBOS Testing Runtime

DBOS provides a testing runtime to facilitate writing unit tests for applications. Before running your tests, create and configure the runtime. In your tests, use the runtime's methods to invoke your application's functions. After your tests finish, destroy the runtime to release resources.

tip

When writing tests, you are responsible for setting up and cleaning up your database schema, for example through migrations.


Create Testing Runtime

createTestingRuntime([userClasses], [configFilePath])

async function createTestingRuntime(userClasses: object[] | undefined = undefined, configFilePath: string = dbosConfigFilePath): Promise<TestingRuntime>

Creates a testing runtime and loads user functions from provided userClasses. By default, all classes and dependencies from the test file are loaded and registered. Accepts an optional path to a configuration file, uses the default path (dbos-config.yaml in the package root) otherwise.

The defaults are generally sufficient as long as classes are at least indirectly referenced from the test file:

testRuntime = await createTestingRuntime();

However, to explicitly create a runtime loading functions from the Hello class and using test-config.yaml:

testRuntime = await createTestingRuntime([Hello], "test-config.yaml");
warning

This method drops and re-creates the DBOS system database. You will lose all persisted system information such as workflow status. Don't run unit tests on your production database!

Methods

runtime.invoke(target, [workflowUUID, params])

invoke<T>(target: T, workflowUUID?: string, params?: WorkflowInvokeParams): InvokeFuncs<T>

Invoke a transaction or communicator. To invoke workflows, use invokeWorkflow or startWorkflow instead. The syntax for invoking function fn in class Cls with argument arg is:

const output = await runtime.invoke(Cls).fn(arg);

You don't supply a context to an invoked function—the testing runtime does this for you.

You can also optionally provide additional parameters for invoke() including the authenticated user and roles and an HTTPRequest. This is especially helpful if you want to test individual functions without running end-to-end HTTP serving. The parameters have the following structure:

interface WorkflowInvokeParams {
readonly authenticatedUser?: string; // The user who ran the function.
readonly authenticatedRoles?: string[]; // Roles the authenticated user has.
readonly request?: HTTPRequest; // The originating HTTP request.
}

runtime.invokeWorkflow(target, [workflowUUID, params])

invokeWorkflow<T>(target: T, workflowUUID?: string, params?: WorkflowInvokeParams): InvokeFuncs<T>

Invoke a workflow and wait for it to complete, returning its result. The syntax for invoking workflow wf in class Cls with argument arg is:

const output = await runtime.invokeWorkflow(Cls).wf(arg);

You don't supply a context to an invoked workflow—the testing runtime does this for you.

As with invoke, you can optionally provide a workflow idempotency key or workflow invocation parameters.

runtime.startWorkflow(target, [workflowUUID, params])

startWorkflow<T>(target: T, workflowUUID?: string, params?: WorkflowInvokeParams): InvokeFuncs<T>

Start a workflow and return a handle to it but do not wait for it to complete. The syntax for starting workflow wf in class Cls with argument arg is:

const workflowHandle = await runtime.startWorkflow(Cls).wf(arg);

You don't supply a context to start a workflow—the testing runtime does this for you.

As with invoke, you can optionally provide a workflow idempotency key or workflow invocation parameters.

runtime.retrieveWorkflow(workflowUUID)

retrieveWorkflow<R>(workflowUUID: string): WorkflowHandle<R>;

Returns a workflow handle for workflow workflowUUID. R is the return type of the target workflow.

runtime.send(destinationUUID, message, [topic, idempotencyKey])

send<T extends NonNullable<any>>(destinationUUID: string, message: T, topic?: string, idempotencyKey?: string): Promise<void>;

Sends message to destinationUUID. Messages can optionally be associated with a topic. You can provide an optional idempotency key to guarantee only a single message is sent even if send is called more than once. For more information, see our messages API tutorial.

runtime.getEvent(workflowUUID, key, [timeoutSeconds])

getEvent<T extends NonNullable<any>>(workflowUUID: string, key: string, timeoutSeconds?: number): Promise<T | null>;

Retrieves a value published by a workflowUUID with identifier key using the events API. A call to getEvent waits for the value to be published and returns null in case of time out.

runtime.getHandlersCallback()

getHandlersCallback(): (req: IncomingMessage | Http2ServerRequest, res: ServerResponse | Http2ServerResponse) => Promise<void>;

Returns a request handler callback for node's native http/http2 server. You can use this callback function to test handlers, for example, using supertest to send a GET request to /greeting/dbos URL and verify the response:

import request from "supertest";

const res = await request(testRuntime.getHandlersCallback()).get(
"/greeting/dbos"
);
expect(res.statusCode).toBe(200);
expect(res.text).toMatch("Hello, dbos! You have been greeted");

runtime.getConfig(key, defaultValue)

getConfig<T>(key: string): T | undefined;
getConfig<T>(key: string, defaultValue: T): T;

Retrieves a property specified in the application section of the configuration.

runtime.queryUserDB(sql, ...params)

queryUserDB<R>(sql: string, ...params: any[]): Promise<R[]>;

Executes a parameterized raw SQL query on the user database. The type R is the return type of the database row.

For example, to query the dbos_hello table created during quickstart and check greet_count, using Jest:

const rows = await testRuntime.queryUserDB<dbos_hello>("SELECT * FROM dbos_hello WHERE name=$1", "dbos");
expect(rows[0].greet_count).toBe(1);

runtime.destroy()

destroy(): Promise<void>

Deconstructs the testing runtime and releases client connections to the database. Please remember to run this method after your tests!