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
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