You need to have msw generate a mockServiceWorker.js
file for you in the public
directory.
npx msw init public
Create file src/mocks/handlers.js
import { rest } from 'msw'; export const handlers = [ rest.get('/api/v1/todos', (_, res, ctx) => { return res( ctx.delay(1500), ctx.json([ { id: '1', title: 'Mocked API', done: false, }, { id: '2', title: 'Task Two', done: false, }, { id: '3', title: 'Task Three', done: false, }, ]), ); }), rest.post('/api/v1/todos', (req, res, ctx) => { return res( ctx.delay(1500), ctx.json({ ...req.body, id: generatedId(), }), ); }), ];
Create file src/mocks/browser.js
import { setupWorker } from 'msw'; import { handlers } from './handlers'; export const worker = setupWorker(...handlers);
Within your index file for the React application:
// ... // Start the mocking conditionally. if (process.env.NODE_ENV === 'development') { const { worker } = require('./mocks/browser'); worker.start(); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root'), ); // ...
Create file src/mocks/server.js
:
import { setupServer } from 'msw/node'; import { handlers } from './handlers'; // Setup requests interception using the given handlers. export const server = setupServer(...handlers);
When setting up for tests, you'll need to setup some Jest config in src/setupTests.ts
.
// src/test/setup-env.js // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; // test/setup-env.js // add this to your setupFilesAfterEnv config in jest so it's imported for every test file import { server } from './mocks/server.js'; beforeAll(() => server.listen()); // if you need to add a handler after calling setupServer for some specific test // this will remove that handler for the rest of them // (which is important for test isolation): afterEach(() => server.resetHandlers()); afterAll(() => server.close());
An example test of running a lifecycle that is being stubbed by msw
looks like the following:
import React from 'react'; import { render, screen, waitForElementToBeRemoved, } from '@testing-library/react'; import App from './App'; describe('rendering the App component', () => { describe('rendering the Todo List', () => { test('shows todo items on successful state cycle', async () => { render(<App />); expect(screen.getByText(/loading/i)).toBeInTheDocument(); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); expect(screen.getByText(/mocked api/i)).toBeInTheDocument(); }); }); });
For overriding tests with a different response, we can inline the server:
import React from 'react'; import { render, screen, waitForElementToBeRemoved } from '../test/test-utils'; import { TodoList } from './App'; import { rest } from 'msw'; import { server } from './mocks/server'; describe('rendering the App component', () => { describe('rendering the Todo List', () => { test('shows todo items on successful state cycle', async () => { render(<TodoList />); expect(screen.getByText(/loading/i)).toBeInTheDocument(); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); expect(screen.getByText(/mocked api/i)).toBeInTheDocument(); }); test('shows a failed request message when server request fails', async () => { server.use( rest.get('/api/v1/todos', (_, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Internal error' })); }), ); render(<TodoList />); expect(screen.getByText(/loading/i)).toBeInTheDocument(); await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); expect(screen.getByText(/request failed/i)).toBeInTheDocument(); screen.debug(); }); }); });