npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@luxass/msw-utils

v0.7.1

Published

Utilities for working with MSW

Readme

@luxass/msw-utils

npm version npm downloads

A collection of utilities for working with MSW (Mock Service Worker).

[!IMPORTANT] This package is still in a very early stage of development and many things are still missing. If you have any suggestions or ideas, please open an issue or a pull request.

Installation

npm install @luxass/msw-utils msw

Requires Node.js >=24.11.0 and MSW >=2.11.0 <3.0.0 || >=3.0.0 <4.0.0. Requires TypeScript >=5.4 when using TypeScript. MSW v3 requires TypeScript >=5.9.

Usage

[!NOTE] This library requires MSW to be set up in your testing environment. Follow the MSW Vitest integration guide for complete setup instructions.

// test/utils.ts
import { createMockFetch } from "@luxass/msw-utils";
import { setupServer } from "msw/node";

export const server = setupServer();
export const mockFetch = createMockFetch({ mswServer: server });
// vitest.setup.ts
import { afterAll, afterEach, beforeAll } from "vitest";

import { server } from "./test/utils.js";

// MSW v3
beforeAll(() => server.listen({ onUnhandledFrame: "error" }));
afterAll(() => server.close());
afterEach(() => server.resetHandlers());

For MSW v2, replace the beforeAll call with:

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
// your-test.test.ts
import { HttpResponse } from "msw";

import { mockFetch } from "./test/utils.js";

test("should fetch version", async () => {
  mockFetch("GET", "/api/v1/versions", () => {
    return HttpResponse.json({ version: "1.0.0" });
  });

  const response = await fetch("/api/v1/versions");
  const data = await response.json();

  expect(data).toEqual({ version: "1.0.0" });
});

If you prefer to avoid importing mockFetch in every test file, you can make it globally available:

// test/utils.ts
import { createMockFetch } from "@luxass/msw-utils";
import { setupServer } from "msw/node";

export const server = setupServer();
export const mockFetch = createMockFetch({ mswServer: server });
// vitest.setup.ts
import { afterAll, afterEach, beforeAll, vi } from "vitest";

import { mockFetch, server } from "./test/utils.js";

vi.stubGlobal("mockFetch", mockFetch);

// MSW v3. With MSW v2, use onUnhandledRequest instead of onUnhandledFrame.
beforeAll(() => server.listen({ onUnhandledFrame: "error" }));
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
// your-test.test.ts
import { HttpResponse } from "msw";

// mockFetch is available globally via vi.stubGlobal
test("should fetch version", async () => {
  mockFetch("GET", "/api/v1/versions", () => {
    return HttpResponse.json({ version: "1.0.0" });
  });

  const response = await fetch("/api/v1/versions");
  const data = await response.json();

  expect(data).toEqual({ version: "1.0.0" });
});

Typed Mock Fetch

The library provides type-safe mocking with TypeScript through two approaches. Registered URL/method pairs constrain response bodies. Unregistered pairs keep MSW's default response body type.

For inferred JSON and text responses, import HttpResponse from this package:

import { HttpResponse } from "@luxass/msw-utils";

This export is the original MSW constructor with improved type signatures. It preserves MSW's runtime behavior, constructor, and other response helpers. It does not add a wrapper or runtime schema validation.

Global Type Registry (Module Augmentation)

You can augment the URLRegistry interface to define type-safe routes globally:

// types/msw-utils.d.ts
import "@luxass/msw-utils";

declare module "@luxass/msw-utils" {
  interface URLRegistry {
    "/api/users": {
      GET: Array<{ id: number; name: string }>;
      POST: { id: number; created: boolean };
    };
    "/api/posts/:id": {
      GET: { id: string; title: string; body: string };
      PUT: { id: string; updated: boolean };
    };
  }
}
// your-test.test.ts
import { HttpResponse } from "@luxass/msw-utils";

import { mockFetch } from "./test/utils.js";

test("should fetch users with type safety", async () => {
  // TypeScript enforces the response type matches URLRegistry
  mockFetch("GET", "/api/users", () => {
    return HttpResponse.json([
      { id: 1, name: "John" },
      { id: 2, name: "Jane" },
    ]);
  });

  const response = await fetch("/api/users");
  const data = await response.json();

  expect(data).toEqual([
    { id: 1, name: "John" },
    { id: 2, name: "Jane" },
  ]);
});

test("should create user with type safety", async () => {
  mockFetch("POST", "/api/users", () => {
    return HttpResponse.json({ id: 1, created: true });
  });

  const response = await fetch("/api/users", {
    method: "POST",
    body: JSON.stringify({ name: "John" }),
  });
  const data = await response.json();

  expect(data).toEqual({ id: 1, created: true });
});

Per-Instance Type Registry

Alternatively, you can use createTypedMockFetch to create a typed wrapper without global augmentation:

// test/utils.ts
import { createMockFetch, createTypedMockFetch } from "@luxass/msw-utils";
import { setupServer } from "msw/node";

interface MyURLs {
  "/api/products": {
    GET: Array<{ id: string; name: string; price: number }>;
    POST: { id: string; created: boolean };
  };
  "/api/orders/:orderId": {
    GET: { orderId: string; total: number; items: string[] };
  };
}

export const server = setupServer();
export const mockFetch = createMockFetch({ mswServer: server });
export const typedMock = createTypedMockFetch<MyURLs>(mockFetch);
// your-test.test.ts
import { HttpResponse } from "@luxass/msw-utils";

import { typedMock } from "./test/utils.js";

test("should fetch products with type safety", async () => {
  // TypeScript enforces the response type matches MyURLs
  typedMock("GET", "/api/products", () => {
    return HttpResponse.json([
      { id: "1", name: "Widget", price: 9.99 },
      { id: "2", name: "Gadget", price: 19.99 },
    ]);
  });

  const response = await fetch("/api/products");
  const data = await response.json();

  expect(data).toHaveLength(2);
});

test("should handle path parameters with type safety", async () => {
  typedMock("GET", "/api/orders/:orderId", ({ params }) => {
    return HttpResponse.json({
      orderId: params.orderId as string,
      total: 99.99,
      items: ["item1", "item2"],
    });
  });

  const response = await fetch("/api/orders/123");
  const data = await response.json();

  expect(data.orderId).toBe("123");
});

Typed batches infer each response body without explicit response type arguments:

typedMock([
  ["GET", "/api/products", () => HttpResponse.json([{ id: "1", name: "Widget", price: 9.99 }])],
  ["POST", "/api/products", () => HttpResponse.json({ id: "1", created: true })],
]);

TypeScript rejects a response that does not match that entry's registered URL and method. Inference also preserves literal fields, nested arrays, tuples, and union responses.

You can still import HttpResponse directly from msw. That import retains MSW's original type signatures, which can require explicit response types in batches.

Both approaches support:

  • Single method endpoints
  • Multiple methods on the same URL
  • Path parameters
  • Batch registration of endpoints

API

createMockFetch(options)

Creates a mockFetch function that uses MSW server under the hood but provides a nicer developer experience.

Parameters

  • options.mswServer: An MSW server instance from setupServer()

Returns

The mockFetch function.

mockFetch(methods, url, resolver)

Register a single endpoint with one or more HTTP methods.

Parameters

  • methods: HTTP method(s) - can be a single method or array of methods
  • url: The endpoint URL pattern
  • resolver: MSW HttpResponseResolver function

mockFetch(endpoints)

Register multiple endpoints at once.

Parameters

  • endpoints: Array of [methods, url, resolver] tuples

createTypedMockFetch(mockFetch)

Creates a type-safe wrapper around mockFetch with inline URL registry. Provides an alternative to module augmentation for users who prefer explicit type parameters.

Parameters

  • mockFetch: A MockFetchFn instance from createMockFetch()

Type Parameters

  • Registry: Record mapping URLs to HTTP methods and their response types

Returns

A type-safe mockFetch wrapper with enforced response types based on the registry.

Example

interface MyURLs {
  "/api/users": {
    GET: User[];
    POST: CreateUserResponse;
  };
}

const mockFetch = createMockFetch({ mswServer: server });
const typedMock = createTypedMockFetch<MyURLs>(mockFetch);

// Type-safe - enforces User[] response
typedMock("GET", "/api/users", () => HttpResponse.json([...yourResponse]));

// Type error - wrong response type
typedMock("GET", "/api/users", () => HttpResponse.json("wrong"));

HttpResponse

The MSW constructor with JSON and text body inference. Import it from @luxass/msw-utils to use typed batches without manual response type arguments.

new HttpResponse(...), explicit type arguments, and MSW's other static response helpers remain available. Responses are still MSW instances.

MSW version differences

mockFetch uses the installed MSW version. It does not change URL matching or resolver values to emulate another version.

Plain named parameters such as /users/:id work on both majors. Set replaceOpenAPIPathParams: true to convert /users/{id} to /users/:id.

MSW v3 replaces its URL matcher. Two differences affect string patterns:

  • Repeated parameters such as /files/:parts+ and /files/:parts* return arrays in v2 and slash-separated strings in v3. For /files/a/b, params.parts is ["a", "b"] in v2 and "a/b" in v3.
  • Inline regex constraints such as /users/:id(\d+) match /users/123 in v2 but not in v3. Plain /users/:id still matches in both. This utility does not remove numeric constraints or accept RegExp URL arguments.

MSW v3 does not manage a cookie store in Node.js. Use the cookies resolver argument for cookie-based logic, not request.headers.get("cookie").

See the MSW v3 migration guide for other upstream changes.

Testing

The shared integration suite runs against MSW 2.11.0, 2.15.0, 3.0.0, and 3.0.3 in isolated Vitest projects. Runtime-guard unit tests run once.

pnpm test
pnpm test --project msw-2
pnpm test --project msw-3
pnpm test --project msw-2-min --project msw-3-min

Runtime Guards

The package also exports runtime type guards for MSW-specific types. Import from @luxass/msw-utils/runtime-guards:

isMSWError(error)

Checks if an error is an MSW internal error. MSW throws internal errors with the name "InternalError" and prefixes error messages with "[MSW]".

With MSW v3 in Node.js, a blocked fetch() rejects with a TypeError whose cause is the MSW error. Pass that cause to isMSWError. The Fetch wrapper itself is not an MSW error.

Parameters

  • error: The error to check (type: unknown)

Returns

true if the error is from MSW (type predicate: error is Error)

Example

import { isMSWError } from "@luxass/msw-utils/runtime-guards";

try {
  // some code that might throw MSW errors
} catch (error) {
  if (isMSWError(error)) {
    console.log("This is an MSW error:", error.message);
  } else {
    console.log("This is not an MSW error");
  }
}

📄 License

Published under MIT License.