Testing, mocks, and coverage
Build reliable tests with Deno.test, permissions, filters, coverage, and resource cleanup
Minimal test
import { assertEquals } from "jsr:@std/assert";
Deno.test("normalizes a user name", () => {
assertEquals(" Ada ".trim(), "Ada");
});
deno test
deno test --filter "user"
deno test --watch
Deno 2.9 has snapshots on the test context with no extra import:
Deno.test("renders a card", async (t) => {
await t.assertSnapshot(renderCard({ title: "Deno" }));
});
Create or refresh snapshots with deno test --update-snapshots and review the __snapshots__/*.snap diff. CI runs plain deno test and must never update expected output.
Tests are sandboxed too. Grant network or temporary-directory access only to the tests that need it; do not run the whole suite with -A because one integration test needs I/O.
Async cleanup
Deno.test("fetches health", async () => {
const controller = new AbortController();
try {
// start resource, assert behavior
} finally {
controller.abort();
}
});
Deno's sanitizers help detect leaked async operations and resources. Note the defaults: since Deno 2.8, only the exit sanitizer is on by default; the op and resource sanitizers are opt-in. Enable leak detection explicitly on the test or step that needs it instead of relying on defaults:
Deno.test({
name: "closes every handle",
sanitizeOps: true,
sanitizeResources: true,
fn: async () => {
// ...
},
});
Conversely, if a test intentionally keeps background work alive, disable the relevant sanitizer on that test with a note explaining why — never disable them globally.
Coverage
deno test --coverage=coverage
deno coverage --lcov --output=coverage.lcov coverage/
Coverage does not replace tests for failure paths, denied permissions, timeouts, and cancellation. CI should run at least fmt --check, lint, check, and test.
Large suites
deno test --changed=origin/main
deno test --shard=1/3
deno test --retry=2
deno test --repeats=3
deno test --trace-leaks
retry tolerates a known flaky failure and passes after one successful attempt; repeats requires every run to pass and helps expose instability. Do not let retries permanently hide a deterministic defect.