DEV Community

Cover image for Testing React With Jest and OpenAPI mocks
Viljami Kuosmanen for epilot

Posted on • Updated on

Testing React With Jest and OpenAPI mocks

Kent C. Dodds recently wrote a very interesting post calling an end to mocking window.fetch when testing React applications:

He was right.

I just recently had to migrate a React project from a fetch based API client implementation to an axios based one 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 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, but here's all you really need to do in your test code to mock a REST endpoint:

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());
Enter fullscreen mode Exit fullscreen mode

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. 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:

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());
Enter fullscreen mode Exit fullscreen mode

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 for openapi-backend:

api.register('validationFail', (c, res, ctx) => res(
  ctx.status(400),
  ctx.json({ error: c.validation.errors }),
));
Enter fullscreen mode Exit fullscreen mode

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:

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();
});
Enter fullscreen mode Exit fullscreen mode

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 makes mocking APIs in React tests a breeze.

Thank you, Kent, and the team behind mswjs/msw! πŸ’™

We are hiring!

Top comments (5)

Collapse
 
srtvprateek profile image
Prateek Srivastava • Edited

Hey Viljami,
nice article πŸ‘,
I have been using Mocklets for my app development, and its been great, specially there dynamic response feature, which allows you to set multiple responses for single api to verify various test cases.

Collapse
 
anttiviljami profile image
Viljami Kuosmanen

Nice! Mocklets seems super cool. I'll definitely have to look into it more. Always love seeing companies popping up innovating in this space!

Collapse
 
kettanaito profile image
Artem Zakharchenko

I absolutely love the use case Viljami illustrates in this article! Super excited to see how MSW integrates with this setup. Keep up the great work!

Collapse
 
joaosousafranco profile image
Joao Franco

Very nice article but leaving me with some questions:

  • how do u think this approach compares to using nock?
  • What do you think about isolating the way your ui code interacts with external dependencies, avoiding the use of apis such as fetch and axios across all ur frontend code?
  • From ur xp using a mock server don’t you think it adds extra complexity to the development lifecycle and CI since you have to setup the server?
Collapse
 
vncz profile image
Vincenzo Chianese

Check out Prism also: it offers OpenAPI 2/3 and Postman Collection mocks and validation: github.com/stoplightio/prism