msw
is great for mocking responses. However, it does not work well when
paired with playwright
in an SSR app. playwright-msw
works (very) well for
mocking browser-initiated requests but does not (and cannot) affect any server-side
requests; this means you cannot override server-side mock request handlers on a
per-test/suite basis - it is an all or nothing approach.
This library aims to fill that gap. Now, there is already a (more advanced) PR to bring this support natively into msw but has not been merged due to time limitations. While the PR remains unmerged, this library can get you some of the way there.
msw^2.0.0
is required:
npm install msw^2.0.0 msw-ssr-interceptor
pnpm install msw^2.0.0 msw-ssr-interceptor
yarn add msw^2.0.0 msw-ssr-interceptor
bun install msw^2.0.0 msw-ssr-interceptor
- Override mock handlers on a per-test basis in SSR apps
- Reset overidden mock handlers in SSR apps
And that is it, no more and no less. The scope of this library is much smaller (to fix the SSR story in msw), and so it may have some gaps in features.
Usage is identical to that of the setupServer
API.
First, setup your mocks:
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw'
export const handlers = [
// Intercept "GET https://example.com/user" requests...
http.get('https://example.com/user', () => {
// ...and respond to them using this JSON response.
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
firstName: 'John',
lastName: 'Maverick',
})
}),
]
Then, somewhere in your app, use the setupRemoteServer
function from msw-ssr-interceptor/node
instead of setupServer
from msw/node
:
// src/mocks/node.js
import { setupRemoteServer } from 'msw-ssr-interceptor/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
Then, wherever you'd like to override/add request handlers:
// src/__tests__/home.test.js
import { setupIntercetptor, http } from 'msw-ssr-interceptor'
const server = await setupInterceptor() // async!
await server.use(
// Overwrite the interception of "GET https://example.com/user" requests...
http.get('https://example.com/user', () => {
// ...and respond to them using this JSON response.
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
firstName: 'John',
lastName: 'Doe',
})
}),
) // also async!
You must use the http
export from msw-ssr-interceptor
and not the one from msw
In your SSR app, you call setupRemoteServer
(exactly how you'd call setupServer
). However,
internally, this also creates a web socket server.
Unlike msw, you cannot use the returned server from setupServer
in your tests to
add/remove handlers. Instead, you must use the return value of setupInterceptor
from msw-ssr-interceptor
. Internally, setupInterceptor
creates a web socket client and informs the server whenever you
add or remove request handlers. It does this by sending over a serialised representation of the handler; this includes a uniquely generated identifier for this request handler. On the server side, this representation is deserialised and then registered with/removed from your msw server running in your SSR app as needed. When a request is matched on the app side,
it sends that request to the client with the corresponding resolver ID which will then handle
the request on client, sending the response back to your SSR app. This approach avoids
function serialisation hell at the expense of network roundtrips.
All serialisation/de-serialisation is handled by seroval
.
As a convenience, a playwright
fixture is exported from msw-ssr-interceptor/playwright
that will inject the interceptor server into each test so you can override server-side fetches
with less boilerplate.
The following is an example of how you might use it with playwright-msw
:
import { expect, test as playwrightTest } from "@playwright/test";
import { http, type Interceptor } from "msw-ssr-interceptor";
import { createInterceptorFixture } from "msw-ssr-interceptor/playwright";
import type { Config, MockServiceWorker } from "playwright-msw";
import { createWorkerFixture } from "playwright-msw";
import { handlers } from "../src/mocks/handlers";
// Export function so we can pass different config as needed
export function testFactory(config: Config) {
return playwrightTest.extend<{
worker: MockServiceWorker;
server: Interceptor;
http: typeof http;
}>({
http,
worker: createWorkerFixture(handlers, config),
server: createInterceptorFixture(),
});
}
const test = testFactory({ waitForPageLoad: true });
export { expect, test };
See Contributing Guide.
Distributed under the MIT License. See LICENSE
for more information.