How to run tests with Bun

In the early days of computing, there were no automated testing tools. Programmers had to rely on manual debugging, often using print statements or other manual methods to identify and fix issues in their code. This was a time-consuming and error-prone process. Test-driven development (TDD) gained prominence in the early 2000s—a software development practice used by developers to write tests for a piece of code before writing the actual code. The tests guide the development process and help ensure the code works as expected.

Bun appears to draw inspiration from this shift towards a more systematic and automated approach to testing. Jest, a popular testing framework, is a reference for Bun testing features. It offers features like snapshot testing, built-in mocking, and other capabilities to streamline the testing workflow. In addition, Bun provides a fast, built-in Jest-compatible test runner. With Bun, developers can enjoy the following features:

  • TypeScript and JSX support

  • Lifecycle hooks for testing components

  • Snapshot testing for tracking and validating UI component changes

  • UI and DOM testing capabilities

  • Watch mode with --watch for continuous testing during development

  • Script preload with --preload to enhance test performance and efficiency

Running a single test with Bun

Tests in Bun are written in JavaScript or TypeScript and use a familiar Jest-like API. We can create test files with names that match specific patterns to be automatically discovered by the test runner. Test files typically have names like *.test{js|jsx|ts|tsx} or *.spec.{js|jsx|ts|tsx} but they can be *_test.{js|jsx|ts|tsx} or *_spec{js|jsx|ts|tsx}.

To run tests using Bun, we employ the following command:

bun test

The test runner explores the working directory to locate files matching the prescribed test file patterns and execute the tests within. Here is an example of the outcome when using the bun test command with the test file shown above:

import { expect, test } from "bun:test";
test("5 + 5", () => {
expect(5 + 5).toBe(10);
});

Let’s break down the provided test:

  • Line 1: We import the expect and test functions from the "bun:test" module.

  • Line 3: We write a test case with the description "5 + 5". The arrow function (() => {) is the test function executed when this test case runs.

  • Line 4: Within the test function, we use the expect function to make an assertion. It checks whether the result of the expression 5 + 5 is equal (toBe(10)) to 10. In other words, it verifies that the sum of 5 and 5 equals 10.

In the above example, a simple test is executed successfully and passed. If a test encounters a failure, the outcome would be different. Let’s see how a test failure scenario unfolds:

import { expect, test } from "bun:test";
test("5 + 5", () => {
expect(6 + 5).toBe(10);
});

As demonstrated, when a test fails, we can precisely pinpoint the location of the failure and understand the reason behind it. This feature provides developers a valuable tool for diagnosing and resolving issues promptly.

Running multiple tests with Bun

Bun’s test runner processes all our tests sequentially within a single environment. This implies that tests are executed one after the other. When a test run is completed, the test runner provides an exit code, which can be vital for automating the build and CI/CD processes to determine the overall test suite outcome.

An exit code of 00 denotes that all tests have passed successfully, while a non-zero exit code (e.g., 11) signifies at least one test failure.

In the following testing setup, we implement similar test cases in two different files to showcase how the bun test efficiently manages and executes tests across multiple files. Let’s observe how the exit code varies based on test outcomes.

Tests that will fail

math.test.ts
test2.test.ts
import { expect, test } from "bun:test";
test("5 + 5", () => {
expect(6 + 5).toBe(10);
});

In this case, a non-zero exit code indicates the failure of a test.

Tests that will pass

Now, let’s fix the first test to make it pass:

math.test.ts
test2.test.ts
import { expect, test } from "bun:test";
test("5 + 5", () => {
expect(6 + 5).toBe(11);
});

Here, we obtain an exit code of 00, signifying the successful execution of all tests. Here is a list of common flags that can be used with the bun test command to customize and control the test execution process.

Flags For the Bun Test Command

Flag

Description

--preload

Used for script preloading

--timeout

Specifies a per-test timeout in milliseconds

--rerun-each <n>

Runs each test a specified number of times (n)

--bail <n>

Aborts the test run early after a predetermined number of test failures

--watch

Watches for changes and reruns tests

--update-snapshots

Updates snapshots

--only

Only runs tests that are marked with test.only() or describe.only()

--todo

Runs tests defined using test.todo() or describe.todo()

Conclusion

Running tests with Bun is exceptionally straightforward, and its compatibility with Jest makes it an outstanding choice as a testing framework for our projects. However, it’s important to note that Bun offers a rich array of features beyond what we’ve covered. These include capabilities such as mock tests, watch mode for continuous testing during development, and more. Delving into these advanced features can further enhance our testing workflow and productivity, making Bun a versatile and powerful tool for comprehensive testing in our projects.

Copyright ©2024 Educative, Inc. All rights reserved