Mocking local dev and tests with msw + @mswjs/data
Tags: react, msw, javascript, typescript
Published: 2022-01-22 23:35:03

## Background

Recently, I found myself needing to mock CRUD operations from an API. At that time, the API was being developed by another engineer. We agreed on the API specs which allowed me to progress on building the UI.

During development, the mocked APIs are useful to build to mock the actual API implementation. 

During testing, it is also valuable to be able to test the actual user interactions. There are amazing blog posts by Kent C. Dodds (author of `@testing-library/react`) on [avoiding testing implementation details](https://kentcdodds.com/blog/testing-implementation-details#implementation-detail-freetesting) and mocking the actual [API over mocking fetch](https://kentcdodds.com/blog/stop-mocking-fetch#example).

In this article, we will go though the approach I went to building this mock server using `msw` by building a simple pet dog CRUD application, that has the following features:

- List all dogs
- Create a dog
- Update a dog
- Delete a dog

Additionally, data can be stored in-memory database provided by a standalone data library `msw/datajs`. This provides the capabilities of describing our data, persisting them in-memory and read/write operations. We will explore writing REST API handlers backed by the data library methods.

## Setup

In this article, l will be building a simple CRUD React application. To help quickly bootstrap my application I will be using the `vitejs` `react-ts` template and Chakra UI components. To help simplify and abstract our data-fetching and manage server state, `react-query` will be used.

For this demo, we will need to install the `msw` libraries and a mock generator `faker`. At the time of writing,  the latest version of `faker` has “[endgamed](https://www.youtube.com/watch?v=R6S-b_k-ZKY)”. For this post, we’ll use version 5.5.3, which still works.

```jsx
yarn add msw @mswjs/data
yarn add faker@5.5.3
```

## Data model

Models are blueprint of data and entities are instances of models. Each model requires a primary key that is a unique ID in a traditional database.

Here, we define our dog model. Each property in the model definition has an initializer that seeds a value and infers the type. Each model must have a primary key that is a unique ID, that we may be familiar with in traditional databases.

```jsx
import { factory, primaryKey } from '@mswjs/data';
import faker from 'faker';

const modelDictionary = {
  dog: {
    id: primaryKey(faker.datatype.uuid),
    breed: () => faker.helpers.randomize(BREEDS),
    age: () => faker.datatype.number(13),
    description: () => faker.lorem.words(5),
    owner: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
  },
};

const db = factory(modelDictionary);
```

## Seeding data

Once the database is created, we can seed it with data. Properties that aren’t set in the `.create` method will be resolved by the model dictionary definition.

```jsx
export function seedDb() {
  db.dog.create({ owner: 'Jerico', breed: 'maltese' });
  db.dog.create({ owner: 'Jerry', breed: 'pug' });
}
```

## Request handlers

These are functions that will mock the API requests from our app. In this app, we will be using the `rest` handlers to mock our REST API. More information on the syntax can be found in the [msw docs](https://mswjs.io/docs/basics/request-matching#rest-api).

```jsx
export const handlers = [
  rest.get<DefaultRequestBody, PathParams, Dog[]>(
    '/api/dogs',
    (_req, res, ctx) => {
      return res(ctx.json(db.dog.getAll()));
    }
  ),
  rest.post<Omit<Dog, 'id'>, PathParams, Dog>('/api/dogs', (req, res, ctx) => {
    const created = db.dog.create(req.body);
    return res(ctx.json(created));
  }),
  rest.delete<DefaultRequestBody, { id: string }, Dog>(
    '/api/dogs/:id',
    (req, res, ctx) => {
      db.dog.delete({ where: { id: { equals: req.params.id } } });
      return res(ctx.status(204));
    }
  ),
  rest.put<Omit<Dog, 'id'>, { id: string }, Dog>(
    '/api/dogs/:id',
    (req, res, ctx) => {
      const updated = db.dog.update({
        where: { id: { equals: req.params.id } },
        data: req.body,
      });
      return res(ctx.json(updated!));
    }
  ),
];
```

Alternatively, `mswjs/data` provides a neat method that actually generates these request handlers using the following. Do note that the generated routes are in the following [conventional format](https://github.com/mswjs/data#generate-rest-api).

```jsx
const handlers = [...db.user.toHandlers('rest')]
```

## Running msw

### In the browser

In our source code we can execute the following line. Note that we may want to conditionally execute this only on our local dev server.

```jsx
import { setupWorker } from 'msw';

setupWorker(...handlers).start();
```

### In the tests

Similarly, to mock API requests in our tests:

```jsx
import { setupServer } from 'msw/node';

const server = setupServer(...handlers);

beforeAll(() => {
  server.listen();
});

afterAll(() => {
  server.close();
});
```

## Implementation

The implementation will not be included in this post, but the full source code can be found in my [repo](https://github.com/jericopingul/msw-data-testing) and deployed [here](https://msw-data-testing.netlify.app/).

## Wrap up

Writing a mock API using `msw` and `mswjs/data` allowed me to develop the UI while the actual API was being developed by another engineer. This setup also allowed me to write the request handlers only once for both my development server and tests. This personally made the effort worthwhile and made writing my tests enjoyable.

I hope this is something that will be of benefit to you, as much as it was for me.

## Further reading

In a more complex application, we could have multiple data models and can have relationships with each other. `mswjs/data`  allows establishing relationships between our models in the docs [here](https://github.com/mswjs/data#model-relationships). 

Additionally, there are more model methods to [explore](https://github.com/mswjs/data#model-methods). I like the way the API is likened to SQL and [take inspiration](https://github.com/mswjs/data#honorable-mentions) from [prisma.io](http://prisma.io). 

`mswjs/data` [supports GraphQL](https://github.com/mswjs/data#graphql-handlers) as well, which I’d love to explore in my next project.