React unit testing using Vitest, RTL and MSW
Tags: typescript, react, msw, vite
Published: 2024-02-15 10:10:23

## **Introduction**

Hello everyone, i'm finally back after almost 5 years since my last post in this community, i would like to apologize to everyone that i couldn't answer their questions, i completely lost access to this account a few years ago and it was a hell of a road to get it back.
Today i would like to share with you a quick tutorial about how to use MSW (Mock service worker) inside React.js application for both testing and development 


## **Getting started**

**What is MSW ?**

MSW or mock service worker is an api mocking library used mostly to mock api calls, it is mainly using browser [Service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) to set up their system and to be able to intercept requests at network levels 

**Why using MSW ?**
Well there are a couple of reasons why we use MSW. 
- For development it is pretty usefull for frontend developers to prevent them being blocked during their tasks in case there are some issues in backend side. We all know that these things occurs sometimes when the server is down or when there is something that should be fixed and we find that we depend on the data that should be returned to continue progressing. However using MSW we can create our mocks exactly with the same structure that we have in backend side and this will completly be on a separated box from what http client we are using either fetch or axios our bussiness logic will remain the same 
- For testing we all know that most of our projects rely on http requests and when creating unit test we find that we are mostly forced to mock these requests inside our tests 

```javascript
import { http } from "./api/myService";
jest.mock("./api/myService");

describe('Fetch data , () => {
    it('should render active user when isActive us true', async () => {
        http.mockResolvedValue(
            {
                id: '1',
                name: 'Mohamed Aymen',
                isActive: STATUS.Active,
            },
        )

        render(<MyComponent />)

        expect(screen.getByTestId('isActive')).toBeInTheDocument()
    })
}
```
In this example we can see that we mocked the returned value of an http request in order to test this behavior.
But what happen when we need this mocked value in other test cases ? 
we will have to mock this request everytime we need to use it 
and what if we need something reusable accros all of our tests? 
**This is where the power of MSW comes in hand :)) **
With MSW the mocks will be created on a separted box and will run in parallel with our application logic as well as tests so we don't need to mock the returned values everytime as will get automatically the responses from our mocks in MSW 

## Setup MSW 
for this tutorial we will need to create a React application first so under you project directory open terminal then run the following command 

`npm create vite@latest`

we will use TypeScript template because i personally think TypeScript is just AWESOME :)) 

![vite typescript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwtw8ox9ybdbp4yruhei.png)

**Install MSW and Axios**
Under /yourdirectory/my-msw-app run this command 

`npm install msw --save-dev`
and 
`npm install axios --save`

Now that MSW is installed let's create our React component. 
To keep this tutorial quick and simple we will try to create a simple component that allows us to send an http requests that return a user with a parameters status with that we can check if this user is Active or not 

first create the componnet `Profile/Profile.tsx` under `/src`

```javascript
import React, { useEffect, useState } from 'react'
import { STATUS } from './consts'
import axios, { AxiosError, AxiosResponse } from 'axios';

const Profile = () => {
    axios.defaults.baseURL = 'http://localhost:8000';
    const [status, setStatus] = useState<STATUS>(STATUS.OFFLINE);

    useEffect(() => {
        axios.get('api/getStatus').then((res: AxiosResponse<{isActive: STATUS }>) => {
            setStatus(res.data.isActive)
        }).catch((err: AxiosError) => {
            console.error({ err })
        })
    }, [])

    return (
        <div>
            {
                status === STATUS.ACTIVE ?
                    <div data-testid="user-active">
                        this user is active
                    </div>
                    :
                    <div data-testid="user-offline">
                        this user is offline
                    </div>
            }

        </div>
    )
}

export default Profile
```
then create a `consts.ts` file to declare our Enum

```javascript
export enum STATUS {
    ACTIVE="ACTIVE",
    OFFLINE = "OFFLINE"
}
```
**initialize our MSW**
Under src create a folder called `msw ` then create inside two files `worker.ts` and `handlers.ts `

```javascript
//Worker.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const serviceWorker = setupWorker(...handlers);
```
In this file we initlize our workers and pass the handlers 

```javascript
//handlers.ts
import { http, HttpResponse } from "msw";
import { STATUS } from "../UserProfile/consts";
export const handlers = [
    http.get("http://localhost:8000/api/getStatus", () => {
        return HttpResponse.json({
            isActive: STATUS.ACTIVE,
        })
    }),
];
```
In this file we declare our handlers and setup our mocks 
as you can see the endpoint `/api/getStatus` is basically the same used in axios so no need to override or rewrite anything as MSW will automatically intercept this request and return our mock

final step is to call the worker inside our `index.tsx `

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

async function enableMocking() {
  if (!Boolean(import.meta.env.VITE_ENABLE_MSW)) {
    return
  }

  const { worker } = await import('./msw/workers')

  // `worker.start()` returns a Promise that resolves
  // once the Service Worker is up and ready to intercept requests.
  return worker.start()
}
enableMocking().then(() => {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});

```

As you can see the msw will be triggered in one condition if the env variable `VITE_ENABLE_MSW` is set to true 

for this we need to create a `.env.local` file and also update scripts in `package.json `

```javascript
//.env.local
VITE_ENABLE_MSW=false
```

```javascript
  "scripts": {
    "dev": "vite",
    "dev-msw": "set VITE_ENABLE_MSW=true && vite",
    "msw-init": "npx msw init ./public --save",
}
```
 
NOTE:
if we want msw to be used in our development env we can  just run our project using `npm run msw-dev` but before that we need to execute `npm run msw-init` in order to regsiter our `mockServiceWorker.js`inside `/public` so that the file will be accessible via `http://localhost:3000/mockServiceWorker.js`.

After running our command we should be able to access our React app at `http://localhost:3000/` then if we want to make sure that msw is working fine we can check our chrome devTools under application=>service worker 

![Image d](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/98im281gvfrsbrljufog.png)

now we are ready to use it  !! 

**Run the project **

Once our project running we can check our `http://localhost:3000/ `to check our app 

![app running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykpgzlcogs5hw2x1e6sb.png)

if eveything was fine we should see this "User is active" text is displayed !! 
if we try to check our network in chrome devTools for example we should see that the request has been successfully intercepted by MSW and we get our mocked result returned to us 

![result status 200](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzj68gx02ws2r6hpndrr.png)

![result json success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wh3tyr8efpu0ulibq77w.png)


As we can see we can use MSW this way during development and specially when our backend is down or blocked we can at least mock some endpoints and continue progressing 


## MSW and React Testing 

In order to setup MSW for unit test we should proceed as follow.
1- install React testing library and Vitest

```javascript
npm install vitest jsdom @testing-library/react @testing-library/jest-dom --save-dev
```
2- go to vite.config.ts and add the following config 

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
  },
})

```
3- update our `msw/worker.ts `
For test purposes we need to change our previous import from
`import { setupWorker } from 'msw/browser'`
to 
`import { setupServer } from 'msw/node'`
thus the file will look like this 

```javascript
//Worker.ts
import { setupServer} from "msw/node";
import { handlers } from "./handlers";

export const serviceWorker = setupServer(...handlers);
```
4- create a file called `src/setupTests.ts` and add the following config

```javascript
import { expect, afterEach, beforeAll, afterAll } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from "@testing-library/jest-dom/matchers";
import { worker } from './msw/workers';
expect.extend(matchers);

afterEach(() => {
  cleanup();
});

// Start worker before all tests
beforeAll(() => { worker.listen() })

//  Close worker after all tests
afterAll(() => {worker.close()})

// Reset handlers after each test `important for test isolation`
afterEach(() => {worker.resetHandlers()})
```
Before each test worker will start in order to setup our handlers then after each test it will reset the handlers to keep our mocks consistent then once all tests are finished the worker will be stoped 
5- update `package.json ` 

```
"scripts": {
    "test:unit": "vitest --root src/"
}
```

6- Now create a unit test for our React component 
First let make sure that our component is rendering without any issue 

```javascript
import { expect, test, describe } from 'vitest'
import { render } from '@testing-library/react';
import Profile from './profile';

describe('profile Component', () => {
    test('should render without crashing', () => {
        const { getByTestId } = render(<Profile />);
        const root = getByTestId('root');
        expect(root).toBeInTheDocument()
    })

})

```
then run :
`npm run test:unit`


![Testing component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rc9d1wuufjoh7tomgos5.png)
and tests are working fine!! 

Now let's try to check if msw is working inside tests, we will try to test if the component is returning the text "this user is active" which mean that the http requests to the msw are returning 200 and that we get what we want.
For that we will use the testid's to check if the component is rendering 

![testid on components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppnkar98vigluz9yha5s.png)

```javascript
test('should render user is active when server retun Status:Active', async() => {
        render(<Profile />);
        await waitFor(() => {
            const section1 = screen.queryByTestId('user-active');
            const section2 = screen.queryByTestId('user-offline');
            expect(section1).toBeInTheDocument();
            expect(section2).toBeNull();
          });
     
    })
```
And Bingo !! our tests are working 


![tests working](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amc39sss1h8q46s89yxj.png)

the full content of the test file: 

```javascript
import { expect, test, describe } from 'vitest'
import { render , screen, waitFor } from '@testing-library/react';
import Profile from './profile';

describe('profile Component', () => {
    test('should render without crashing', () => {
        const { getByTestId } = render(<Profile />);
        const root = getByTestId('root');
        expect(root).toBeInTheDocument()
    })
    test('should render user is active when server retun Status:Active', async() => {
        render(<Profile />);
        await waitFor(() => {
            const section1 = screen.queryByTestId('user-active');
            const section2 = screen.queryByTestId('user-offline');
            expect(section1).toBeInTheDocument();
            expect(section2).toBeNull();
          });
     
    })
})

```

## Conclusion 

MSW is a powerfull tool to use in our projects for development as well as testing it allows us to have a central setup mocks for our tests and removes bunch of noizy mocks from our test files also it keeps them consistent.
Any questions or issues please let us know in your comments 
Cheers!!