Setting up a static server for Vitest tests

I have an app that loads images and icons from external URLs. During testing with Vitest, I needed a way to serve these static files so components could access them just like in production. After considering MSW for mocking, I opted for a simpler approach: running a static server during tests and using its URL from environment variables.

Dependencies

You’ll need to install the sirv package to serve static files:

Terminal window
npm install --save-dev sirv

Add global setup to vite.config.ts

First, I added a global setup to my Vitest configuration in vite.config.ts:

vitest.config.ts
/// <reference types="vitest/config" />
import { defineConfig, loadEnv } from "vite";
export default defineConfig(() => {
return {
test: {
globalSetup: ["./src/core/tests/vitest.global-setup.ts"],
},
};
});

Create the global setup file

Next, I created a global setup file that starts the static server before tests run and stops it after they finish:

vitest.global-setup.ts
import { startStaticServer, stopStaticServer } from "./static";
import path from "path";
export async function setup() {
const staticDir = path.resolve(__dirname, "..", "..", "..", "public");
await startStaticServer({ staticDir });
}
export async function teardown() {
await stopStaticServer();
}

Implement the static server

The core of the solution is the static server implementation:

static.ts
import sirv from "sirv";
import http from "node:http";
import type { AddressInfo } from "node:net";
let server: http.Server | undefined;
interface StartStaticServerOptions {
staticDir: string;
port?: number;
host?: string;
dev?: boolean;
single?: boolean;
allowedOrigin?: string;
}
export async function startStaticServer(options: StartStaticServerOptions) {
if (server) {
console.warn(
"Static server is already running. Please stop it before starting a new one.",
);
return;
}
console.log("Starting static file server...");
const {
staticDir,
port = 0,
host = "127.0.0.1",
dev = true,
single = true,
allowedOrigin = "*",
} = options;
const assets = sirv(staticDir, {
dev,
single,
});
server = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept",
);
if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}
assets(req, res);
});
await new Promise<void>((resolve, reject) => {
server!.listen(port, host, () => {
const addressInfo = server!.address() as AddressInfo;
const serverUrl = `http://${addressInfo.address}:${addressInfo.port}`;
process.env.VITE_STATIC_SERVER_URL = serverUrl;
console.log(
`Static file server started on ${serverUrl} serving from ${staticDir}`,
);
resolve();
});
server!.once("error", (err) => {
console.error("Failed to start static file server:", err);
server = undefined;
reject(err);
});
});
}
export async function stopStaticServer() {
console.log("Stopping static file server...");
if (server) {
await new Promise<void>((resolve, reject) => {
server!.close((err) => {
if (err) {
console.error("Error stopping static file server:", err);
reject(err);
return;
}
console.log("Static file server stopped.");
server = undefined;
resolve();
});
});
} else {
console.log("Static file server was not running.");
}
}

Using the static server in tests

Now in my tests, I can pass the static server URL to my components:

<my-component
assets-url=${`${process.env.VITE_STATIC_SERVER_URL}/assets`}
></my-component>

The components can then access files like http://127.0.0.1:56545/assets/icons/expand-more.svg without any issues, just like they would in production (of course, with a different base URL).