This page details the implementation of Deno's built-in test runner. It covers test discovery, the internal execution pipeline, the communication between Rust and JavaScript via ops, and the reporting system.
Deno's test framework is integrated directly into the CLI. When a user runs deno test, the system orchestrates file discovery, module graph building, and execution within specialized workers. The framework supports both standard test files and documentation tests (JSDoc).
System Architecture and Data Flow
Sources: cli/tools/test/mod.rs120-147 cli/ops/testing.rs32-58 cli/js/40_test.js1-40
Deno classifies tests into three modes via the TestMode enum: Documentation (fenced code blocks in JSDoc/Markdown), Executable (standard test files), or Both.
The function collect_specifiers is used to gather test files based on glob patterns provided in Flags or deno.json. It filters for supported script extensions and respects the workspace settings.
| Component | Responsibility | Source |
|---|---|---|
TestMode | Determines if a specifier is Doc, Executable, or Both | cli/tools/test/mod.rs124-132 |
TestFilter | Filters tests by substring, regex, or include/exclude lists | cli/tools/test/mod.rs153-209 |
collect_specifiers | Recursively walks directories to find test targets | cli/tools/test/mod.rs85 |
Sources: cli/tools/test/mod.rs120-150 cli/tools/test/mod.rs153-209
Test execution involves a Rust-side coordinator and a JavaScript-side runner.
When a test file is loaded, calls to Deno.test() trigger op_register_test. This stores the test description and the V8 function handle in a TestContainer.
Test Registration Sequence
Sources: cli/ops/testing.rs129-182 cli/tools/test/mod.rs220-225 cli/js/40_test.js8-19
Deno implements several sanitizers to ensure tests don't leave the environment in a "dirty" state:
Deno.exit() from killing the test runner. In 40_test.js, assertExit wraps the test function and uses setExitHandler from ext:deno_os/30_os.js to catch premature exit attempts.The op_pledge_test_permissions and op_restore_test_permissions ops allow tests to run with restricted permissions that are restored after the test completes using a PermissionsHolder and a Uuid token.
Sources: cli/ops/testing.rs84-123 cli/js/40_test.js184-205
Results are streamed from the workers to the main thread via a TestEvent channel. The TestReporter trait defines how these events (Plan, Wait, Result, Output, UncaughtError) are presented to the user.
Reporter Entity Relationship
Sources: cli/tools/test/mod.rs105-110 cli/tools/test/mod.rs98-102
The Deno Language Server provides a dedicated testing implementation in cli/lsp/testing/. It allows IDEs to discover tests statically and execute them individually.
find_test_name and find_test_fn_body in cli/lsp/code_lens.rs extract test metadata using SWC AST traversal. TestStepCollector in cli/lsp/testing/collectors.rs specifically looks for t.step() or BDD-style it() calls.TestRun struct in cli/lsp/testing/execution.rs handles the conversion of LSP TestRunRequestParams into CLI-equivalent execution jobs. It manages a CancellationToken to allow clients to abort running tests.Sources: cli/lsp/code_lens.rs66-100 cli/lsp/testing/collectors.rs34-43 cli/lsp/testing/execution.rs163-200
Deno provides a polyfill for the node:test module in ext/node/polyfills/testing.ts. This implementation bridges Node's testing API to Deno's internal runner. It uses activeTestSinks to track the currently executing test body and correctly attribute unhandled rejections or uncaught exceptions to the active test.
Sources: ext/node/polyfills/testing.ts92-113 ext/node/polyfills/testing.ts165-181
Benchmarking shares significant infrastructure with the test framework but uses distinct events and reporters.
op_register_bench (in cli/ops/bench.rs) into a BenchContainer.BenchStats, which includes min, max, avg, and various percentiles (p75, p99, etc.).JsonReporter or ConsoleReporter. Results are encapsulated in BenchResult.Sources: cli/tools/bench/mod.rs121-132 cli/tools/bench/mod.rs145-153 cli/tools/bench/mod.rs198-209
Refresh this wiki