writing-dev-server-tests
Writing HMR/Dev Server Tests
Dev server tests validate hot-reloading robustness and reliability.
File Structure
test/bake/bake-harness.ts- shared utilities:devTest,prodTest,devAndProductionTest,Devclass,Clientclasstest/bake/client-fixture.mjs- subprocess forClient(page loading, IPC queries)test/bake/dev/*.test.ts- dev server and hot reload teststest/bake/dev-and-prod.ts- tests running on both dev and production mode
Test Categories
bundle.test.ts- DevServer-specific bundling bugscss.test.ts- CSS bundling issuesplugins.test.ts- development mode pluginsecosystem.test.ts- library compatibility (prefer concrete bugs over full package tests)esm.test.ts- ESM features in developmenthtml.test.ts- HTML file handlingreact-spa.test.ts- React, react-refresh transform, server componentssourcemap.test.ts- source map correctness
devTest Basics
import { devTest, emptyHtmlFile } from "../bake-harness";
devTest("html file is watched", {
files: {
"index.html": emptyHtmlFile({
scripts: ["/script.ts"],
body: "<h1>Hello</h1>",
}),
"script.ts": `console.log("hello");`,
},
async test(dev) {
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
await dev.patch("index.html", { find: "Hello", replace: "World" });
await dev.fetch("/").expect.toInclude("<h1>World</h1>");
await using c = await dev.client("/");
await c.expectMessage("hello");
await c.expectReload(async () => {
await dev.patch("index.html", { find: "World", replace: "Bar" });
});
await c.expectMessage("hello");
},
});
Key APIs
files: Initial filesystem statedev.fetch(): HTTP requestsdev.client(): Opens browser instancedev.write/patch/delete: Filesystem mutations (wait for hot-reload automatically)c.expectMessage(): Assert console.log outputc.expectReload(): Wrap code that causes hard reload
Important: Use dev.write/patch/delete instead of node:fs - they wait for hot-reload.
Testing Errors
devTest("import then create", {
files: {
"index.html": `<!DOCTYPE html><html><head></head><body><script type="module" src="/script.ts"></script></body></html>`,
"script.ts": `import data from "./data"; console.log(data);`,
},
async test(dev) {
const c = await dev.client("/", {
errors: ['script.ts:1:18: error: Could not resolve: "./data"'],
});
await c.expectReload(async () => {
await dev.write("data.ts", "export default 'data';");
});
await c.expectMessage("data");
},
});
Specify expected errors with the errors option:
await dev.delete("other.ts", {
errors: ['index.ts:1:16: error: Could not resolve: "./other"'],
});
More from oven-sh/bun
zig-system-calls
Guides using bun.sys for system calls and file I/O in Zig. Use when implementing file operations instead of std.fs or std.posix.
137implementing-jsc-classes-cpp
Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.
80writing-bundler-tests
Guides writing bundler tests using itBundled/expectBundled in test/bundler/. Use when creating or modifying bundler, transpiler, or code transformation tests.
79implementing-jsc-classes-zig
Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.
79slowest-tests
Find the top-N slowest test files in CI from a recent BuildKite run, optionally posting the results to a Slack channel as a formatted table. Use when asked to find slow CI tests, "what's making CI slow", or to post a slow-test report to Slack.
2javascriptcore-garbage-collector
JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace, HeapAnalyzer, finalize.
2