Define tests with a Jest-like API imported from the built-in bun:test
module. Long term, Bun aims for complete Jest compatibility; at the moment, a limited set of expect
matchers are supported.
Basic usage
To define a simple test:
import { expect, test } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
Jest-style globals
Tests can be grouped into suites with describe
.
import { expect, test, describe } from "bun:test";
describe("arithmetic", () => {
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
test("2 * 2", () => {
expect(2 * 2).toBe(4);
});
});
Tests can be async
.
import { expect, test } from "bun:test";
test("2 * 2", async () => {
const result = await Promise.resolve(2 * 2);
expect(result).toEqual(4);
});
Alternatively, use the done
callback to signal completion. If you include the done
callback as a parameter in your test definition, you must call it or the test will hang.
import { expect, test } from "bun:test";
test("2 * 2", done => {
Promise.resolve(2 * 2).then(result => {
expect(result).toEqual(4);
done();
});
});
Timeouts
Optionally specify a per-test timeout in milliseconds by passing a number as the third argument to test
.
import { test } from "bun:test";
test("wat", async () => {
const data = await slowOperation();
expect(data).toBe(42);
}, 500); // test must run in <500ms
In bun:test
, test timeouts throw an uncatchable exception to force the test to stop running and fail. We also kill any child processes that were spawned in the test to avoid leaving behind zombie processes lurking in the background.
🧟 Zombie process killer
When a test times out and processes spawned in the test via Bun.spawn
, Bun.spawnSync
, or node:child_process
are not killed, they will be automatically killed and a message will be logged to the console.
test.skip
Skip individual tests with test.skip
. These tests will not be run.
import { expect, test } from "bun:test";
test.skip("wat", () => {
// TODO: fix this
expect(0.1 + 0.2).toEqual(0.3);
});
test.todo
Mark a test as a todo with test.todo
. These tests will not be run.
import { expect, test } from "bun:test";
test.todo("fix this", () => {
myTestFunction();
});
To run todo tests and find any which are passing, use bun test --todo
.
bun test --todo
my.test.ts:
✗ unimplemented feature
^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.
0 pass
1 fail
1 expect() calls
With this flag, failing todo tests will not cause an error, but todo tests which pass will be marked as failing so you can remove the todo mark or fix the test.
test.only
To run a particular test or suite of tests use test.only()
or describe.only()
. Once declared, running bun test --only
will only execute tests/suites that have been marked with .only()
. Running bun test
without the --only
option with test.only()
declared will result in all tests in the given suite being executed up to the test with .only()
. describe.only()
functions the same in both execution scenarios.
import { test, describe } from "bun:test";
test("test #1", () => {
// does not run
});
test.only("test #2", () => {
// runs
});
describe.only("only", () => {
test("test #3", () => {
// runs
});
});
The following command will only execute tests #2 and #3.
bun test --only
The following command will only execute tests #1, #2 and #3.
bun test
test.if
To run a test conditionally, use test.if()
. The test will run if the condition is truthy. This is particularly useful for tests that should only run on specific architectures or operating systems.
test.if(Math.random() > 0.5)("runs half the time", () => {
// ...
});
const macOS = process.arch === "darwin";
test.if(macOS)("runs on macOS", () => {
// runs if macOS
});
test.skipIf
To instead skip a test based on some condition, use test.skipIf()
or describe.skipIf()
.
const macOS = process.arch === "darwin";
test.skipIf(macOS)("runs on non-macOS", () => {
// runs if *not* macOS
});
test.todoIf
If instead you want to mark the test as TODO, use test.todoIf()
or describe.todoIf()
. Carefully choosing skipIf
or todoIf
can show a difference between, for example, intent of "invalid for this target" and "planned but not implemented yet."
const macOS = process.arch === "darwin";
// TODO: we've only implemented this for Linux so far.
test.todoIf(macOS)("runs on posix", () => {
// runs if *not* macOS
});
test.each
To return a function for multiple cases in a table of tests, use test.each
.
const cases = [
[1, 2, 3],
[3, 4, 5],
];
test.each(cases)("%p + %p should be %p", (a, b, expected) => {
// runs once for each test case provided
});
There are a number of options available for formatting the case label depending on its type.
%p | pretty-format |
%s | String |
%d | Number |
%i | Integer |
%f | Floating point |
%j | JSON |
%o | Object |
%# | Index of the test case |
%% | Single percent sign (% ) |
Matchers
Bun implements the following matchers. Full Jest compatibility is on the roadmap; track progress here.