Testing React With Jest and OpenAPI mocks
Tags: react, testing, openapi, mock
Published: 2020-06-12 09:23:15

Kent C. Dodds recently wrote a [very interesting post](https://kentcdodds.com/blog/stop-mocking-fetch) calling an end to mocking `window.fetch` when testing React applications:

{% twitter 1270359083409965057 %}

He was right.

I just recently had to migrate a React project from a fetch based API client implementation to an [axios based one](https://github.com/anttiviljami/openapi-client-axios) where the tests heavily relied on mocking `global.fetch`. It very quickly became apparent why this is not good practice.

I ended up having to write my own test utility that would mock both fetch and the new API client. It never looks good when you have to change tests to prove your code didn't change anything for the user.

As a better alternative, Kent suggests using a *Mock Service Worker*. More specifically the [msw module](https://github.com/mswjs/msw) to essentially run a mock backend as a service worker that intercepts all outgoing API requests to handle them.

### Setting up msw

Setting up a mock backend with msw for your React tests turns out to be a fairly easy process. To get a deeper picture you should check out [Kent's original post](https://kentcdodds.com/blog/stop-mocking-fetch), but here's all you really need to do in your test code to mock a REST endpoint:

```javascript
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/pets', (req, res, ctx) => {
    const pets = [{ id: 1, name: 'Garfield', type: 'cat' }];
    return res(ctx.json({ pets }));
  }),
);

beforeAll(() => server.listen());
afterAll(() => server.close());
```

One of the reasons this is extremely cool is because it avoids the pain of having to start up a real local mock backend, such as an express server that needs to be bound to a specific port on the host running the test.

This helps keep your tests fast and simple to run, as they should be.

### Even better with OpenAPI

As someone who works a lot with API backends that (hopefully!) provide Swagger/OpenAPI definitions, I had already been mocking my backends in React tests using OpenAPI mocks with [openapi-backend](https://github.com/anttiviljami/openapi-backend). When I learned about `msw`, I was thrilled!

It turns out `msw` together with `openapi-backend` is the perfect combination for mocking REST apis.

To provide a full mock for an API, all I need is to create a mock backend with openapi-backend using the API definition and tell msw to use it:


```javascript
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import OpenAPIBackend from 'openapi-backend';
import definition from './path/to/definition.json';

// create our mock backend with openapi-backend
const api = new OpenAPIBackend({ definition });
api.register('notFound', (c, res, ctx) => res(ctx.status(404)));
api.register('notImplemented', async (c, res, ctx) => {
  const { status, mock } = api.mockResponseForOperation(c.operation.operationId);
  ctx.status(status);
  return res(ctx.json(mock));
});


// tell msw to intercept all requests to api/* with our mock
const server = setupServer(
  rest.all('/api/*', async (req, res, ctx) => api.handleRequest(
    {
      path: req.url.pathname,
      query: req.url.search,
      method: req.method,
      body: req.bodyUsed ? await req.json() : null,
      headers: { ...req.headers.raw },
    },
    res,
    ctx,
  )),
);

beforeAll(() => server.listen());
afterAll(() => server.close());
```

Now instead of having to write your own mock handlers for each operation, they're generated from the response schemas and examples defined in the OpenAPI document.

What's more: any time the API definition changes, all your mocks will be automatically updated giving you further confidence your app is compatible with the new API version.

### Enabling Request Validation

When testing, it's often very useful to make sure your application is actually sending the correct requests to the API.

Working with OpenAPI definitions has the benefit that API operations are well defined and requests can be automatically validated using JSON schema.


To enable request validation during tests, you can simply register the [validationFail handler](https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#validationfail-handler) for openapi-backend:

```javascript
api.register('validationFail', (c, res, ctx) => res(
  ctx.status(400),
  ctx.json({ error: c.validation.errors }),
));
```

When running tests, a malformed call to an API endpoint will now result in a 400 Bad Request error from the mock backend, alongside a useful error message telling you what's wrong with the request.

### Custom Handlers

In some tests it might make sense to provide a different mock than the default one as provided by openapi-backend.

Registering your own mock for an API operation in a test is as simple as calling `api.register()` with the operationId and a mock handler:

```javascript
it('should call getPets operation', () => {
  // given
  const mockResponse = [{ id: 2, name: 'Odie' }];
  const mockHandler = jest.fn((c, res, ctx) => res(ctx.json(mockResponse)));
  api.register('getPets', mockHandler);

  // when
  // render(<MyComponent />)...

  // then
  expect(mockHandler).toBeCalled();
});
```

### Conclusion

Finding out about msw was a major game changer for my React testing. This in combination with the auto-mocking capabilities of [openapi-backend](https://github.com/anttiviljami/openapi-backend) makes mocking APIs in React tests a breeze.


Thank you, Kent, and the team behind [mswjs/msw](https://github.com/mswjs/msw)! 💙

[![We are hiring!](https://dev-to-uploads.s3.amazonaws.com/i/fnbppwjezekl7t56nfxz.png)](https://epilot.cloud/en/career-engineering/)