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:
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
:
/// <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:
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:
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).