Runtime configuration for Mock Service Worker (MSW) with localStorage
Mock Service Worker (MSW) is a neat mocking library that uses the Service Worker API to intercept network requests. Its API is really straightforward and flexible which made it a perfect fit a tooling project I was working on. We are looking for the ability to easily set up specific scenarios and share then between non-technical audiences to demonstrate our app’s behavior under certain conditions.
We have predefined mocked responses for our various endpoints based on our API spec, each of which
has a unique name or key. So for example the POST /items
endpoint for creating new items has a
mocked error response called USER_LIMIT_REACHED
which occurs when user reaches their item creation
limit. This way I can assign the endpoint a unique key and then share the stub setup as an encoded
query parameter to allow folks to easily share a scenario.
Simple Mock Service Worker and React setup
MSW supports both Node and Browser mock implementations, as we’re looking to mock in the browser
there are a few additional steps beyond the Node set-up we need to complete, but we can start by
running: npm install msw --save-dev
and then move on to defining our mock request handlers
(the functions that control the mocks).
We’re using the REST API mocking feature of MSW for this, if you’re looking for the GraphQL API check out the dedicated docs for GraphQL.
The first extra step for browser mocking is the need for a Service Worker file. We can quickly generate this by providing our public directory path to the following command:
Up next, we need a browser.ts
file, in which we are going to create a worker instance with our
request handlers. I import the MSW and pass it my handlers, this is an array of request handlers
functions which we will look at in a moment:
With those files added we’re ready to define some request handlers.
Defining request handlers
For MSW, we use a src/mocks/handlers.ts
file that will contain our request handler functions, in
which we define each API call we want to mock based on the path and method. For example:
Typically, these are staticly defined, however for our case we want a series of response resolvers to ensure we can opt in and out of mocks at runtime.
Runtime handlers
We have a separate app that allows users to configure which mocks they want to use for which endpoint, this is all saved into a JSON object stored in localStorage, the JSON output of which looks like this:
Orchestrator
Now we have our config saved in JSON we can update the handlers code to iterate over the object and dynamically register handlers for each endpoint in our mock config.
Note: I cut some corners here as my use-cases only mapping POST
and GET
requests, and our JSON
data structure only supports one method per endpoint. In our ./handlers.ts
file we start by
exporting an array called handlers
which becomes the argument to setupWorker
:
Then importing our array we exported above into browser.ts
like so:
But quickly jumping back to our generateHandlers()
function from above, notice how we’re passing
mockOrchestrator
as the callback for every provider? This syntax is very similar to Express’
middleware, for example:
Here we can augment the mochOrchestrator
to read from our data store in localStorage and retrieve
the config JSON and to read from our stub mapping and dynamically set up each mock. Here is a full
example:
We use req.passthrough()
a few times as a way to
opt-out of mocking for particular endpoints or in error scenarios (no config), this ensures MSW
calls the original endpoint rather than mocking.
Now we have completed the basic set-up for dynamic stubs with Mock Service Worker. We read from our localstorage, iterate over the endpoints registering a handler for each path and method. with a handler that sets a response based on the information in the localStorage config.
Happy (dynamic) mocking!