|
| 1 | +--- |
| 2 | +title: Testing a React Application Integrating MSW with Vitest |
| 3 | +date: 2022-05-04 |
| 4 | +published: true |
| 5 | +description: Testing a React application. How to integrate MSW with vitest, a unit testing library. |
| 6 | +categories: |
| 7 | + - testing |
| 8 | + - react |
| 9 | +cover_image: https://cdn.hashnode.com/res/hashnode/image/upload/v1651098470347/9coA_akWr.png |
| 10 | +--- |
| 11 | + |
| 12 | +### Introduction |
| 13 | + |
| 14 | +The fifth part in my ongoing series on how to test a modern React application. This time I'll go over how to integrate MSW with [Vitest](https://vitest.dev/), our unit-test framework. Most applications have to fetch data from the backend server. In order to have full coverage, we should mock these requests. But, what is mocking? |
| 15 | + |
| 16 | +> Make a replica or imitation of something |
| 17 | +
|
| 18 | +_Oxford Languages_ |
| 19 | + |
| 20 | +The idea is to create an imitation of a request coming in from the backend. This comes with its own set of advantages. We can directly manipulate what we want the _response_ to be to test for more scenarios. In the app we previously created we could test for fetching 0 posts, 100 posts, posts with no text, and so on and so forth. |
| 21 | + |
| 22 | +The app in question: |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +This is very powerful! We can test for common use cases or edge cases that the user may run into. And at the end of the day, the most important thing is confidence in our tests. |
| 27 | + |
| 28 | +### What is MSW? |
| 29 | + |
| 30 | +[MSW](https://mswjs.io/) is a mocking library that is extremely simple to use. |
| 31 | + |
| 32 | +> Mock by intercepting requests on the network level. Seamlessly reuse the same mock definition for testing, development, and debugging. |
| 33 | +
|
| 34 | +_ mswjs.io_ |
| 35 | + |
| 36 | +Normally, this would be the expected interaction: |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +But, with the added addition of MSW, we will add a new step. |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | +Awesome! 😎 Let's get this set up with our application. For reference[here is the project](https://github.com/diballesteros/react-testing) we've been using up to this point. |
| 45 | + |
| 46 | +### Configuration files for MSW |
| 47 | + |
| 48 | +First, let's install our new library: |
| 49 | + |
| 50 | +```sh |
| 51 | +npm install msw --save-dev |
| 52 | + |
| 53 | +yarn add msw --dev |
| 54 | +``` |
| 55 | + |
| 56 | +In our `src` directory let's create a `mocks` older where we'll keep the handlers for the requests. The MSW team refers to this as _mock definitions_. Inside the `mocks` folder create a `handlers.js`. |
| 57 | + |
| 58 | +Here we can export our handler functions. Since we're doing normal REST requests, let's import `rest` from MSW. |
| 59 | + |
| 60 | +```js |
| 61 | +import { rest } from 'msw'; |
| 62 | +``` |
| 63 | + |
| 64 | +In order for MSW to recognize the request, we must provide the exact _method_ and _path_ and export it from an array. |
| 65 | + |
| 66 | +```js |
| 67 | +export const handlers = [ |
| 68 | + rest.get('https://jsonplaceholder.typicode.com/posts', null), |
| 69 | +]; |
| 70 | +``` |
| 71 | + |
| 72 | +Here we can replace `null` with what we actually want MSW to return to us. This is a function known as a _response resolver_. Returning the following: |
| 73 | + |
| 74 | +- `req`, information about a matching request; |
| 75 | + |
| 76 | +- `res`, a functional utility to create the mocked response; |
| 77 | + |
| 78 | +- `ctx`, a group of functions that help to set a status code, headers, body, etc. of the mocked response. |
| 79 | + |
| 80 | +Let's return our own custom response for these posts. |
| 81 | + |
| 82 | +```js |
| 83 | +import { rest } from 'msw'; |
| 84 | + |
| 85 | +export const handlers = [ |
| 86 | + rest.get('https://jsonplaceholder.typicode.com/posts', (req, res, ctx) => { |
| 87 | + return res( |
| 88 | + ctx.status(200), |
| 89 | + ctx.json([ |
| 90 | + { |
| 91 | + body: 'This is a body', |
| 92 | + id: 1, |
| 93 | + title: 'Title', |
| 94 | + userId: 1, |
| 95 | + }, |
| 96 | + ]) |
| 97 | + ); |
| 98 | + }), |
| 99 | +]; |
| 100 | +``` |
| 101 | + |
| 102 | +Sweet, now we have our handler set up for MSW 🚀. |
| 103 | + |
| 104 | +### Configuration files for Vitest |
| 105 | + |
| 106 | +MSW sets up a server for us to intercept the requests. But we have to create an instance of the server. Create a `server.js` file in our `mocks` folder: |
| 107 | + |
| 108 | +```js |
| 109 | +import { setupServer } from 'msw/node'; |
| 110 | +import { handlers } from './handlers'; |
| 111 | + |
| 112 | +// Here we import the handler created! |
| 113 | +export const server = setupServer(...handlers); |
| 114 | +``` |
| 115 | + |
| 116 | +In our `vite.config.js` lets add an entry for our setup files in the `test` object: |
| 117 | + |
| 118 | +```js |
| 119 | +setupFiles: ['./src/setup.js'], |
| 120 | +``` |
| 121 | + |
| 122 | +Let's create this `setup.js` file in our `src` directory. This is to correctly reset the server with every test execution: |
| 123 | + |
| 124 | +```js |
| 125 | +import { server } from './mocks/server'; |
| 126 | + |
| 127 | +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); |
| 128 | +afterAll(() => server.close()); |
| 129 | +afterEach(() => server.resetHandlers()); |
| 130 | +``` |
| 131 | + |
| 132 | +Now we're all set up and ready to test! Let's implement this in our **Vitest ** test. |
| 133 | + |
| 134 | +### Mocking our API request in Vitest |
| 135 | + |
| 136 | +Let's revamp our test file: |
| 137 | + |
| 138 | +```js |
| 139 | +import React from 'react'; |
| 140 | +import { |
| 141 | + render, |
| 142 | + screen, |
| 143 | + waitForElementToBeRemoved, |
| 144 | +} from '@testing-library/react'; |
| 145 | +import userEvent from '@testing-library/user-event'; |
| 146 | +import App from './App'; |
| 147 | + |
| 148 | +describe('Testing our React application', () => { |
| 149 | + it('Fetch posts', async () => { |
| 150 | + render(<App />); |
| 151 | + |
| 152 | + expect(screen.getByText(/Modern React Testing/i)).toBeDefined(); |
| 153 | + |
| 154 | + userEvent.click(screen.getByRole('button', { name: 'Fetch Posts' })); |
| 155 | + |
| 156 | + await waitForElementToBeRemoved(() => |
| 157 | + screen.queryByLabelText('loading') |
| 158 | + ); |
| 159 | + |
| 160 | + expect(screen.getByRole('heading', { level: 3 })).toBeDefined(); |
| 161 | + }); |
| 162 | +}); |
| 163 | +``` |
| 164 | + |
| 165 | +We removed the library for `@testing-library/jest-dom` as it is no longer necessary. But, now our test should be passing with green! |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | +Also, since our test is running in a node environment we need to polyfill our fetch function in the original `App.jsx` |
| 170 | + |
| 171 | +```bash |
| 172 | +npm install cross-fetch |
| 173 | +``` |
| 174 | + |
| 175 | +Just import it at the very top: |
| 176 | + |
| 177 | +```js |
| 178 | +import fetch from 'cross-fetch'; |
| 179 | +``` |
| 180 | + |
| 181 | +### Sidenote |
| 182 | + |
| 183 | +If you had been following along my other articles you may have noticed I changed the version of a dependency: `@testing-library/user-event`. I was having an issue firing off the button click. |
| 184 | + |
| 185 | +I downgraded it to 13.5.0 and called the click event directly from `userEvent`. |
| 186 | + |
| 187 | +You can find the entire project in this [repository with the updated list of dependencies](https://github.com/diballesteros/react-testing). |
| 188 | + |
| 189 | +### Wrapping it up |
| 190 | + |
| 191 | +We now have a powerful tool at our disposal to mock requests as we continue to create unit tests! In the next article, we'll go over how to set up Cypress.io. |
| 192 | + |
| 193 | +More content at [Relatable Code](https://relatablecode.com) |
| 194 | + |
| 195 | +## Let's connect |
| 196 | + |
| 197 | +If you liked this feel free to connect with me on [LinkedIn](https://www.linkedin.com/in/relatablecode) or [Twitter](https://twitter.com/relatablecoder) |
| 198 | + |
| 199 | +Check out my free developer roadmap and weekly tech industry news in my [newsletter](https://relatablecode.substack.com/). |
0 commit comments