Skip to content

Using Bun

Bun can be used as a test runner and/or package manager. It can be used as a package manager in combination with any other test runner, or as a test runner in combination with any other package manager.

Installing Bun is as simple as running:

Terminal window
curl -fsSL https://bun.sh/install | bash

You can also check out the official Bun installation documentation for more information.

To get started with Bun as a test runner, install the @types/bun and typescript packages using your preferred package manager:

Terminal window
npm i -D @types/bun typescript

Create a tsconfig.json file:

tsconfig.json
{
"compilerOptions": {
// enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"allowJs": true, // allow importing `.js` from `.ts`
"types": ["bun"],
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags
"useUnknownInCatchVariables": true,
"noPropertyAccessFromIndexSignature": true
},
"include": ["./tests/**/*.ts", "./global-setup.ts", "./types.d.ts"]
}

Then, add a test script to your package.json:

package.json
{
"scripts": {
"test": "tsc && bun test"
}
}

Running tsc is optional, but it is recommended to catch any TypeScript errors before running your tests.

You can also check out the official Bun documentation for TypeScript for more information.

The PocketIC server needs to be started before running tests and stopped once they’re finished running. This can be done by creating a global-setup.ts file in your project’s root directory:

global-setup.ts
import { beforeAll, afterAll } from 'bun:test';
import { PocketIcServer } from '@dfinity/pic';
let pic: PocketIcServer | undefined;
beforeAll(async () => {
pic = await PocketIcServer.start();
const url = pic.getUrl();
process.env.PIC_URL = url;
});
afterAll(async () => {
await pic?.stop();
});

This file can be configured to run with bun test by creating a bunfig.toml file in your project’s root directory:

bunfig.toml
[test]
preload = ["./global-setup.ts"]

To improve the type-safety of using process.env.PIC_URL, add a types.d.ts file in your project’s root directory:

types.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
PIC_URL: string;
}
}
}
export {};

Bun tests are very similar to tests written with Jest, Jasmine, or Vitest so they will feel very familiar to developers who have used these frameworks before.

The basic skeleton of all PicJS tests written with Bun will look something like this:

tests/example.spec.ts
import { resolve } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import { type Actor, PocketIc } from '@dfinity/pic';
// Import the declarations generated for your canister,
// see the Canister declarations guide
import { idlFactory, type _SERVICE } from './declarations/backend.did';
// Define the path to your canister's WASM file.
// icp-cli writes it to `.icp/cache/artifacts/<canister name>`.
const WASM_PATH = resolve(
import.meta.dir,
'..',
'.icp',
'cache',
'artifacts',
'backend',
);
// The `describe` function is used to group tests together
// and is completely optional.
describe('backend', () => {
// Define variables to hold our PocketIC instance
// and an actor to interact with our canister.
let pic: PocketIc;
let actor: Actor<_SERVICE>;
// The `beforeEach` hook runs before each test.
//
// This can be replaced with a `beforeAll` hook to persist canister
// state between tests.
beforeEach(async () => {
// create a new PocketIC instance
pic = await PocketIc.create(process.env.PIC_URL);
// Setup the canister and actor
const fixture = await pic.setupCanister<_SERVICE>({
idlFactory,
wasm: WASM_PATH,
});
// Save the actor for use in tests
actor = fixture.actor;
});
// The `afterEach` hook runs after each test.
//
// This should be replaced with an `afterAll` hook if you use
// a `beforeAll` hook instead of a `beforeEach` hook.
afterEach(async () => {
// tear down the PocketIC instance
await pic.tearDown();
});
// The `it` function is used to define individual tests
it('should greet', async () => {
const response = await actor.greet('PicJS');
expect(response).toEqual('Hello, PicJS!');
});
});

You can also check out the official Bun test runner documentation for more information on writing tests.

PicJS downloads the pocket-ic binary from a postinstall script, which Bun blocks by default. Add @dfinity/pic as a trusted dependency in your package.json so the binary is fetched — see allowing the install script for the other package managers:

package.json
{
"trustedDependencies": ["@dfinity/pic"]
}