Testing
In this guide, you'll learn how to test your DBOS applications.
DBOS code can be easily tested in any unit-testing framework.
We'll show you how to write unit tests for the Hello
example class shipped by npx @dbos-inc/create
, using Jest or Vitest.
Overview
Testing DBOS code involves the following steps:
- Installing and configuring your test tool
- Creating a test
- Setting up the app and system database before running tests
- Configuring and launching DBOS before running DBOS test code
- Calling DBOS functions
- Shutting down DBOS at the end of the test
- Running the tests, usually from a helper script in
package.json
Using jest
Set Up Jest
If you haven't already, install jest.
npm i --save-dev jest @types/jest ts-jest
Set up a typical jest.config.js
, such as:
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testRegex: '((\\.|/)(test|spec))\\.ts?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePaths: ['./'],
};
Start a template test file, with a name such as main.test.ts
:
import { DBOS } from '@dbos-inc/dbos-sdk';
import { app, dbos_hello, Hello } from './main';
import request from 'supertest';
describe('operations-test', () => {
// Your test will go here...
});
To make it easy to run your tests, create a scripts
entry in package.json
:
"scripts": {
"test": "npx dbos migrate && jest"
}
Launching DBOS
Before executing any test code that uses DBOS, DBOS should be configured and launched. This is often placed in beforeAll
or beforeEach
, rather than in each test function.
beforeAll(async () => {
DBOS.setConfig({
name: 'my-app',
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
});
await DBOS.launch();
});
See DBOS.launch
for launch options.
Setting Up App Databases Prior To Launch
You are responsible for setting and cleaning up database tables before and after tests.
In our example, we run Knex migrations with npx dbos migrate
as part of our testing script in package.json
, but this could have been done within the test instead. For example, if you are using TypeORM, the schema can be set up from within the test itself using DBOS.createUserSchema
:
beforeEach(async () => {
DBOS.setConfig({
name: 'my-app',
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
});
await DBOS.launch();
await DBOS.dropUserSchema();
await DBOS.createUserSchema();
});
Resetting The System Database
If your tests reuse workflow IDs, it is necessary to clean out the DBOS system database to ensure a consistent starting point for testing. Dropping the system database with DBOS.dropSystemDB
must be done after DBOS is configured (so the database connection parameters are known), but prior to launch (so that the system database is not yet in use):
DBOS.setConfig({
name: 'my-app',
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
});
await DBOS.dropSystemDB();
await DBOS.launch();
Testing Functions
Once DBOS is launched, a test can invoke workflows, transactions, and steps directly. Functions on the DBOS
class can be used to interact with workflows and check database status during the course of the test.
For example:
test('test-transaction', async () => {
const res = await Hello.helloTransaction('dbos');
expect(res).toMatch('Hello, dbos! You have been greeted');
// Check the greet count.
const rows = (await DBOS.queryUserDB('SELECT * FROM dbos_hello WHERE name=$1', ['dbos'])) as dbos_hello[];
expect(rows[0].greet_count).toBe(1);
});
In this code, we invoke Hello.helloTransaction
with the input string "dbos"
, and verify its output is as expected.
Testing HTTP Endpoints
If you are testing HTTP handling capabilities, these can be tested using a framework such as supertest.
Import the testing package:
import request from "supertest";
Launch your app's HTTP server, or launch DBOS with your app server:
beforeAll(async () => {
DBOS.setConfig({
name: 'my-app',
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
});
await DBOS.launch({ expressApp: app });
});
Use the handler callback during the course of unit-testing. In this code, we send a GET
request to our /greeting/dbos
URL and verify its response:
test('test-endpoint', async () => {
const res = await request(app).get('/greeting/dbos');
expect(res.statusCode).toBe(200);
expect(res.text).toMatch('Hello, dbos! You have been greeted');
});
Cleaning Up
Finally, after your tests, you should clean up DBOS and release its resources. Generally, this is done in afterAll
(or afterEach
):
afterAll(async () => {
await DBOS.shutdown();
});
Run Tests
Now let's run the tests!
npm run test
You should see the test results similar to the following:
PASS src/main.test.ts
operations-test
✓ test-transaction (21 ms)
✓ test-endpoint (17 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.247 s, estimated 2 s
Further Reading
You can find the source code for this tutorial in operations.test.ts.
Using vitest
Using vitest
is similar to using jest
.
First, create a vitest.config.ts
file for your project if it doesn't already have one:
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node", // Use "node" for backend tests
include: ["test/**/*.test.ts"], // Adjust this to match your test file locations
},
});
Then, write test code. Basic test code in vitest
is identical to code for jest
:
import { beforeEach, afterEach, describe, expect, it } from "vitest";
import { DBOS, parseConfigFile } from "@dbos-inc/dbos-sdk";
import { MyWorkflow } from "../dbos/operations";
describe("dbos functions", () => {
beforeEach(async () => {
DBOS.setConfig({
name: 'my-app',
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
});
await DBOS.launch();
});
afterEach(async () => {
await DBOS.shutdown();
});
it("runs-dbos-code", async () => {
await MyWorkflow.backgroundTask(4);
expect(1).toBe(1);
});
});
For convenience, add a test
(or similar) script to your package.json
file:
"scripts": {
"test": "pnpm vitest run"
}
Note the use of the run
command. Without it, vitest
may enter a continuous testing mode where it watches file changes and re-executes tests. As the DBOS executor does not support "hot reload" of workflow code, this mode will not work consistently.
Finally, call your test
script:
npm run test
Output similar to the following should be produced:
✓ test/dbos.test.ts (1 test) 758ms
✓ dbos functions > runs-dbos-code 757ms
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 11:08:00
Duration 2.39s (transform 41ms, setup 0ms, collect 1.40s, tests 758ms, environment 0ms, prepare 75ms)
DBOS TestingRuntime
In prior versions of the TypeScript SDK, a TestingRuntime
was used to set up DBOS. This approach is obsolete.
- Instead of creating a
TestingRuntime
, just callDBOS.setConfig()
andDBOS.launch()
from your tests. - Calls to
TestingRuntime
functions can usually be replaced with identical calls toDBOS
functions. - In most cases, application functions can be called directly within the tests. Older app code can be invoked using
DBOS.invoke
instead of viaTestingRuntime.invoke
.