Simulation environments
SimEnvironment connects production-style HTTP requests to simulated services.
It owns HTTP interception and the origins registered within it.
Register a service
Section titled “Register a service”Every registered service implements one method:
interface SimService { handle(request: Request): Promise<Response> | Response;}SimApi implements this interface. You can also register a small hand-written
service when the API and resource model does not suit an endpoint:
import { SimEnvironment, type SimService } from "@kensio/simnaril";
const healthService: SimService = { handle(request) { const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/health") { return Response.json({ status: "ready" }); }
return new Response("Not found", { status: 404 }); },};
using environment = new SimEnvironment();environment.register("https://status.example.com", healthService);One service handles every path at its registered origin. The service receives a
Web Platform Request and returns a Response.
Handle requests outside the simulation
Section titled “Handle requests outside the simulation”An environment rejects requests to unregistered origins by default. This prevents a test from reaching a network service by accident.
Node’s fetch() wraps the Simnaril error in a network error. Inspect cause to
read the original UnclaimedOriginError:
import { UnclaimedOriginError } from "@kensio/simnaril";
try { await fetch("https://unregistered.example.com/data");} catch (error) { if ( error instanceof TypeError && error.cause instanceof UnclaimedOriginError ) { console.error(error.cause.method); console.error(error.cause.origin); console.error(error.cause.url); }}Set unhandledRequest to "passthrough" when the process must also reach real
network services:
using environment = new SimEnvironment({ unhandledRequest: "passthrough",});Registered origins remain simulated. Requests to other origins pass through to the network.
Dispose the environment
Section titled “Dispose the environment”Dispose the environment as soon as the application has finished using it:
const environment = new SimEnvironment();
try { environment.register("https://api.example.com", api); await runApplication();} finally { environment.dispose();}dispose() is safe to call more than once. A disposed environment cannot accept
new registrations.
Only one active environment can own an origin. A second registration for the same origin throws an error. Disposal releases the origin for another environment.
Different active environments can own different origins. Their registered services and state stay separate.
Call a service without interception
Section titled “Call a service without interception”Call SimApi.handle() directly when a test only needs the HTTP behavior of one
API:
const response = await api.handle( new Request("https://api.example.com/v1/widgets"),);Direct handling avoids process-wide HTTP interception. It still runs routing, request decoding, middleware, state operations, error translation, and response encoding.