Skip to main content
workerd has a comprehensive test suite covering C++ unit tests, JavaScript runtime tests, Node.js compatibility tests, and Web Platform Tests.

Test types

.wd-test files

.wd-test files are Cap’n Proto config files that define test workers. These are the primary way to test JavaScript APIs and worker behavior.
using Workerd = import "/workerd/workerd.capnp";

const unitTests :Workerd.Config = (
  services = [(
    name = "my-test",
    worker = (
      modules = [(name = "worker", esModule = embed "my-test.js")],
      compatibilityDate = "2024-01-01",
      compatibilityFlags = ["nodejs_compat"],
    ),
  )],
);
Key elements:
  • modules - Embed JavaScript/TypeScript files
  • compatibilityFlags - Enable specific compatibility behaviors
  • bindings - Service bindings, JSON, KV, and other resources
  • durableObjectNamespaces - Durable Object configurations
Define the test in a corresponding .js or .ts file:
import { strictEqual } from 'node:assert';

export default {
  async test() {
    strictEqual(1 + 1, 2);
  }
};

C++ unit tests

KJ-based unit tests use the kj_test() Bazel macro:
#include <kj/test.h>

namespace workerd {
namespace {

KJ_TEST("basic arithmetic") {
  KJ_EXPECT(1 + 1 == 2);
  KJ_EXPECT(5 - 3 == 2);
}

}  // namespace
}  // namespace workerd
Common test macros:
KJ_EXPECT(condition);           // Test passes if true
KJ_ASSERT(condition);           // Abort test if false
KJ_EXPECT_THROW(type, code);    // Expect specific exception
KJ_EXPECT_THROW_MESSAGE(msg, code);  // Expect exception with message

Node.js compatibility tests

Run specific Node.js compatibility tests:
just node-test zlib
just node-test crypto

Web Platform Tests

Run Web Platform Tests:
just wpt-test urlpattern
just wpt-test streams

Running tests

Run all tests

bazel test //src/...
# or
just test

Run a specific test

bazel test //src/workerd/api/tests:encoding-test@
just test //src/workerd/api/tests:encoding-test@

Stream test output for debugging

just stream-test //src/workerd/api/tests:encoding-test@

Watch mode

Automatically re-run tests when files change:
just watch test //src/workerd/api/tests:encoding-test@

Test variants

Every test automatically generates multiple variants:
  • name@ - Default variant (oldest compat date, 2000-01-01)
  • name@all-compat-flags - Newest compat date (2999-12-31), all flags enabled
  • name@all-autogates - All autogates enabled + oldest compat date
The @ suffix is required in target names. For example: //src/workerd/io:io-gate-test@
To find the right target name, check the BUILD.bazel file in the same directory for wd_test() or kj_test() rules:
bazel query //src/workerd/api/tests:all

Creating new tests

Scaffold a new test

just new-test //src/workerd/api/tests:my-test

Scaffold a new WPT test

just new-wpt-test my-feature

Testing best practices

Do not submit untested code

Do not submit code you haven’t tested. Even the best programmers usually write code that doesn’t work on the first try.
If you are not able to build your code and run it locally to verify that it does what you expect, do not submit code changes. Include unit tests for any new functionality you add.

Test at the right level

  • JavaScript APIs - Use .wd-test files
  • C++ implementation details - Use C++ unit tests
  • Node.js compatibility - Use or extend Node.js compat tests
  • Web standards - Use or extend Web Platform Tests

Write focused tests

Each test should verify one specific behavior:
// GOOD: focused test
export const basicEncoding = {
  async test() {
    const encoder = new TextEncoder();
    const result = encoder.encode('hello');
    strictEqual(result.length, 5);
  }
};

// BAD: testing too many things
export const everything = {
  async test() {
    // Tests encoding, decoding, edge cases, errors...
  }
};

Test error cases

Don’t just test the happy path:
export const errorHandling = {
  async test() {
    const encoder = new TextEncoder();
    throws(() => {
      encoder.encode(null);
    }, TypeError);
  }
};

Use appropriate assertions

import { strictEqual, deepStrictEqual, throws } from 'node:assert';

// Value equality
strictEqual(result, expected);

// Deep object equality
deepStrictEqual(obj1, obj2);

// Exception testing
throws(() => code(), ErrorType);
throws(() => code(), /error message/);

Benchmarks

Run performance benchmarks:
just bench mimetype
just bench crypto

Code coverage

Generate a code coverage report (Linux only):
just coverage //src/workerd/api/...

Debugging tests

Stream output

By default, test output is buffered. Stream output for debugging:
just stream-test //path/to/test@

AddressSanitizer

Run tests with AddressSanitizer to detect memory errors:
just build-asan
just test-asan //path/to/test@

Test organization

Tests should live near the code they test:
src/workerd/api/
├── encoding.c++
├── encoding.h
└── tests/
    ├── encoding-test.wd-test
    ├── encoding-test.js
    └── BUILD.bazel

Compatibility flags in tests

When testing features behind compatibility flags:
const flagTest :Workerd.Config = (
  services = [(
    name = "flag-test",
    worker = (
      modules = [(name = "worker", esModule = embed "test.js")],
      compatibilityDate = "2024-01-01",
      compatibilityFlags = [
        "nodejs_compat",
        "experimental_feature"
      ],
    ),
  )],
);

Next steps

Documentation guidelines

Learn about documentation requirements

I/O subsystem

Understand the I/O architecture

Build docs developers (and LLMs) love