How to mock APIs in Storybook with MSW (Mock Service Worker)

Photo by Clémence Taillez on Unsplash

Storybook is a fantastic tool to document your components, review them, and to work in isolation.

As Jest, being a developer tool, you don’t want to call any API when you develop or showcase your components. Why? APIs can be slow, unreachable, or unpredictable.

Storybook official documentation recommends four ways to mock API calls:

  1. Don’t use any mock at all: use presentational components wrapped into some kind of “connected” providers. They just receive all properties from a parent, which is easy to replicate in your story.
  2. Mock the Provider wrapping your component. This trick works well for Redux connected components. It could work for any Provider calling an API if your code is structured in that way as well.
  3. Mock the ES6 import in Webpack, which is possible if you use the function from a third-party library or a custom utils file. The mock will be global for all your stories, avoiding some redundancies but being less specific.
  4. Use some tools to intercept calls at a lower level and modify the response, as MSW (Mock Service Worker) does.

This article is focused on this fourth solution as it provides some benefits:

  • The mocks can be attached and specific to each story
  • Mocking `import` can be heavy to implement
  • In my case, the component directly call the API, making usage of a Provider non-appropriate

Summary:

  • Create a Storybook public folder
  • Install Mock Service Worker (MSW)
  • Create a mock in a story
  • Bonus: use local images in your stories

Create a Storybook public folder

The library Mock Service Worker will need to have access to a config file to run. Before installing it, we’ll create a public folder accessible from the browser when we run Storybook.

Note: my Storybook configuration files (main.js, preview.js, etc…) are in the folder .storybook at the root of my project. Make sure all next paths match your setup.

Step 1: create a public folder into .storybook. In my case .storybook/public/.

Step 2: in your package.json, edit your Storybook start command and eventually the build one if you have it. Add the option -s .storybook/public at the end. -s is a shortcut for --static-dir.

Before:

npm run start-storybook -p 6006 -c ./.storybook

After:

npm run start-storybook -p 6006 -c ./.storybook -s .storybook/public

Install Mock Service Worker (MSW)

We’re going to add Mock Service Worker (MSW) to our project and make it available globally.

Usually, Service Workers offer better cache control on websites and web apps. These Workers stand between your front-end and the network, catching all requests being made.

Instead of managing the cache, MSW allows you to catch calls to the network. You can set up whatever you need in place of the official response: your application will act as usual without noticing it’s a mocked response.

Step 1: install the package as a dev dependency.

npm i -D msw

Step 2: initiate the MSW config and let it creates a file in our Storybook public folder recently created (don’t forget to adapt the path to your folder structure)

npx msw init .storybook/public/

After having run this command, the library creates the file mockServiceWorker.js in your public folder. Don’t delete it as explained inside as it is needed to start the service worker.

If everything went well, you should now be able to access this file when you run Storybook. In my case, the URL is:

http://localhost:6006/mockServiceWorker.js

Step 3: start MSW from your Storybook preview.js file by adding…

This line at the beginning of the file:

import { setupWorker, rest } from ‘msw’;

These lines at the end of the file:

// Storybook executes this module in both bootstrap phase (Node)
// and a story's runtime (browser). However, we cannot call `setupWorker`
// in Node environment, so we need to check if we're in a browser.
if (typeof global.process === 'undefined') {
// Create the mockServiceWorker (msw).
const worker = setupWorker();
// Start the service worker.
worker.start();
// Make the `worker` and `rest` references available globally,
// so they can be accessed in stories.
window.msw = { worker, rest };
}

If everything is well configured, when you run Storybook, you should see a message in your console informing you that Mock Service Worker is started:

[MSW] Mocking enabled.

Create a mock in a story

Step 1: Assuming you are using the Component Story Format (CSF) to write your stories, we are going to wrap our Story into a custom <Provider />:

export default {
title: 'MyComponent',
component: MyComponent,
decorators: [(story) => <Provider>{story()}</Provider>],
};
const Provider = ({ children }) => {
return <>{children}</>;
};

Step 2: Assuming we want to:

  1. mock API calls to https://my-api.com/catalog?productCodes=5252178073,2034090082;
  2. return this JSON:
{
"data":[
{
"code":5252178073
},
{
"code":2034090082
}
]
}

… we can extend our Provider with the MSW worker:

const Provider = ({ children }) => {
const { worker, rest } = window.msw;
worker.use(
rest.get("https://my-api.com/catalog", (req, res, ctx) => {
const productCodes = req.url.searchParams.get("productCodes").split(",");
const data = productCodes.map((productCode) => ({
product: {
code: productCode,
},
}));
return res(ctx.json({ data }));
})
);
return <>{children}</>;
};

I let you explore the MSW documentation to find more details about handling requests with the library. It is well written.

Step 3: we need to clean our mock when we leave the story. If we don’t do it, some unexpected behaviors could happen when we check some other stories. It is as easy as adding this line to our <Provider />.

useEffect(() => () => worker.resetHandlers());

Here is our final <Provider /> code:

const Provider = ({ children }) => {
const { worker, rest } = window.msw;
useEffect(() => () => worker.resetHandlers());worker.use(rest.get('https://my-api.com/catalog', (req, res, ctx) => {
const productCodes = req.url.searchParams.get('productCodes').split(',');
const data = productCodes.map((productCode) => ({
product: {
code: productCode,
},
}));
return res(ctx.json({ data }));
}));
return <>{children}</>;
};

If everything is ok, when you open your freshly mocked story, you should see in your console something like this:

[MSW] 16:10:03 GET https://my-api.com/catalog (200)

And your component should now receive your mock data instead of the original API call. 🎉

Bonus: use local images

Initially, when I started with Storybook, I liked to use Unsplash images. They are elegant, and the server is reliable.

But… what is more reliable and fast than my local machine?

Instead of calling Unsplash images, I just have to save the pictures in my Storybook public directory (the first part of this guide) and use a relative path in my story.

Before:

<Image src=”https://source.unsplash.com/1vMz2_MclrM/500x500/" />

After:

<Image src=”/images/living-room-500x500.jpeg” />

Conclusion

Eh voilà! Now I have total control over my API calls and my images. My stories load faster, are more stable, and don’t need any backend to be developed.

Of course, don’t overuse this technic: it’s always better to have simple components receiving just a few arguments and not handling any logic.

Officially, Mock Service Worker should only be used with Storybook to recreate some pages. But it can be useful in some circumstances when your components don’t have a conventional implementation.

Frontend Engineer @ Pelostudio