Integration stories: elevating Storybook with Mock Service Worker
Tags: mockserviceworker, storybook, react, javascript
Published: 2021-04-28 17:51:42

During our last [product cycle](https://www.process.st/shape-up/) at [Process Street](https://www.process.st/), our development experience was heavily aided by combining [Mock Service Worker (MSW)](https://mswjs.io/) and [Storybook](https://storybook.js.org/). It's a powerful union of tools worth knowing about.

![Storybook with MSW](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gg550jaotkmkalacu91.png)

## Building a rich text editor

At Process Street, we're adding rich text documents ("Pages") to our app to help people document their processes faster without having to learn right away about our more structured workflow models.

We chose [Slate.js](https://docs.slatejs.org/) to be the engine of our text editing features, topped off with UI composed by [Chakra](https://chakra-ui.com/) components.

## You don't need a re-write to improve developer experience

Process Street is one of many teams who has done its darnedest to keep up with the rapidly evolving JavaScript landscape whilst iterating rapidly on product. The product started as an [AngularJS (1.5.6)](https://code.angularjs.org/1.5.6/docs/api) application and has gradually adopted more and more [React](https://reactjs.org/) components using [react2angular](https://github.com/coatue-oss/react2angular).

The surface area of our product is already huge, and the frankenstein-state of our UI architecture adds some...friction...to the development experience in certain ways. Instead of loading up the entire application and depending on a locally running API server, we do all of our new UI work in React starting with Storybook.

## Getting integrated

In my 3+ years of using Storybook to build user interfaces, most of the story-able components end up being leaves or very close to leaves in the DOM tree: the ones that are basically taking props and rendering JSX. This is a great pattern for rendering myriad states as named stories, but it's always been a challenge to move up the tree and achieve what is essentially an integration test, but in the form of an interactive story.

Modern React features and patterns make the demand for integration stories even greater. With the rising popularity and leverage of React's [hook](https://reactjs.org/docs/hooks-intro.html) and [context](https://reactjs.org/docs/context.html#gatsby-focus-wrapper) APIs along with adopting more colocated network fetching patterns (e.g., [react-query](https://react-query.tanstack.com/) and [Apollo Client](https://www.apollographql.com/docs/react)) in place of a global store like Redux, it's becoming more common and instinctual to build opaque components (meaning you don't pass them props) that fetch their own data via hooks and context providers.

The same patterns and tools that have allowed us to write more powerful, loosely coupled, and extensible components have left us in somewhat of a pickle when trying to express these components as stories without going through a kind of surgery to separate prop-driven UI components from wrapping components that take care of sourcing those props.

Enter Mock Service Worker.

>Mock by intercepting requests on the network level. Seamlessly reuse the same mock definition for testing, development, and debugging.

**By combining MSW with Storybook, you can express components anywhere in the DOM tree as interactive stories.** Here's how I got an integration story set up for that Pages feature.

## Implement a working MVP in the app

Similar to testing, you can either build your story after you have a working feature, or use the story to drive the component's development. I like starting with a low-fidelity working feature in the app, then moving to Storybook to refine the UI.

All of the necessary API endpoints for this feature already existed, so I opted to build the MSW handlers based on real data from the server. To do so, I observed the network traffic in the developer console while exercising the feature. After that I copied (via right-click) the relevant requests and responses related to the feature's interaction points.

![copy fetch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/976mjiapk70qfvygfc73.png)
 
![copy response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq15i1p823x05xay1jpb.png)
 
## Wire up MSW for Storybook

I'm going to focus specifically on how we integrated Storybook with MSW, ignoring setup. Check out [this article](https://blog.logrocket.com/using-storybook-and-mock-service-worker-for-mocked-api-responses/) for a more thorough walk through of setting everything up. While we're not using it yet in our app, I recently learned there is even a [Storybook addon](https://storybook.js.org/addons/msw-storybook-addon) that simplifies the integration. For educational purposes, I'll still walk through the DIY setup we currently use.

Given a [standard MSW setup](https://mswjs.io/docs/getting-started/install), first make sure the service worker is kicked off in `preview.js`.

```js
// .storybook/preview.js

import { worker } from '../src/mocks/browser';

// this allows you to simply use `worker.use` in your story and/or story decorators
worker.start();
```

In my case there was a lot of API responses that would clutter the story itself, so I exported an object with named keys from a file called `story-data`.

```tsx
// story-data.ts
export const data = {
  latest: { ... }
}
```

With the response data in place, import `data` and configure the `worker.use` API from MSW using the Storybook template pattern.

```tsx
import { data } from './story-data'
const Template: Story<Args> = (props) => {
  worker.use(
    rest.get("/latest", (req, res, ctx) => {
      return res(ctx.json(data.latest))
    },
    ...
  )
  return <Component {...props} />
}
export const Demo = Template.bind({})
```

I prefer to put `worker.use` in the story component itself instead of a decorator because I often end up using Storybook args to influence the MSW response data.


```tsx
const Template: Story<Args> = ({ loading, ...props }) => {
  worker.use(
    rest.get("/latest", (req, res, ctx) => {
      if (loading) {
        return res(
          ctx.delay(1000 * 60),
          ctx.json(data.latest)
        )
      }
      return res(ctx.json(data.latest))
    },
    ...
  )
  return <Component {...props} />
}
```

Another thing to remember is you can often leverage the request object data (`req`) to influence the response data.

```tsx
const Template: Story<Args> = ({ loading, ...props }) => {
  worker.use(
    rest.get("/latest/:id", (req, res, ctx) => {
      if (loading) {
        return res(
          ctx.delay(1000 * 60),
          ctx.json(data.latest)
        )
      }
      return res(ctx.json(data.someLookup[req.params.id]))
    },
    ...
  )
  return <Component {...props} />
}
```

From here, you can get creative combining Storybook arguments and request data to simulate full interaction with a remote data source. Now in addition to observing with and interacting with specific UI states, we get to leverage MSW and Storybook to represent specific _server_ and _network_ states. Similar to the ethos of integration tests using [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), we're testing and exercising our components in a way that is much closer to how a user will interact with them in production, which is a good, good thing.

![](https://media.giphy.com/media/26tOZ42Mg6pbTUPHW/giphy.gif)

If you're interested in working at Process Street, [we're hiring](https://angel.co/company/process-street/jobs)!