Develop and test React apps with React Query, MSW and React Testing Library
Tags: react, testing, webdev
Published: 2021-05-01 13:00:27

---
canonical_url: https://www.denniskortsch.com/blog/develop-test-react-apps-react-query-msw-react-testing-library/
---

In this article we will develop a basic CRUD React app without having an API in place. Instead we will make use of Mock Service Worker to intercept & mock our fetch calls. React Query will be used as a data fetching library and we will follow a test-first approach using React Testing Library.

[React-Query](https://react-query.tanstack.com/): For data fetching.
[MSW](https://mswjs.io/): To intercept & mock our API calls. 
[React Testing Library](https://testing-library.com/docs/react-testing-library/intro/): Write our tests.

---

Let's imagine a scenario where you have the specifications and requirements for your UI already but the API your app is supposed to interact with is not ready yet. Only the contract itself is already defined.

The API is roughly defined as:

```
GET /users, returns all users 
GET /users/:id returns a user by id
POST /users, creates a new user
PUT /users/:id, updates an existing user by id
DELETE /users/:id, deletes an existing user by primary key.
```

So it is a basic **C**reate **R**ead **U**pdate **D**elete feature set.

Hence our app will have the following features:

- list users with user name
- show a specific user details
- update a specific user
- create a new user
- delete user

__Design TRIGGER Warning: For the sake of simplicity we will not care about Design or UX in this guide. We will focus solely on raw feature demonstration. So be warned, this will look like 💩!__

## The Setup

Start with creating a `create-react-app`:

```bash
npx create-react-app react-tdd
```

And install our extra dependencies:

`yarn add react-query`

`yarn add -D msw @mswjs/data`

### Clean up and React Query

Let's get at least the basic app foundation going before writing our first tests. First let's rip out everything we don't need from `src/App.js`, add a `QueryClientProvider` from `react-query` and a placeholder `Users` component.

```javascript
import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Users />
    </QueryClientProvider>
  );
}

export default App;
```

Users.js
```javascript
export function Users() {
  return <div>Users</div>;
}
```

### Get Mock Service Worker up and running

Because we are not developing against an API and we also don't want to mock our fetch calls nor `react-query` itself we use `msw` to intercept fetch calls and return mock data. To set up `msw` we first need to run its initial setup script which will create the service worker script for us. 

`npx msw init public/ --save`

Next we create 3 new files: 

`src/mocks/db.js`.

```javascript
import { factory, primaryKey } from '@mswjs/data';

export const mockUsers = [
  {
    id: '1',
    name: 'Alice',
    email: 'alice@aol.com',
  },
  {
    id: '2',
    name: 'Bob',
    email: 'bob@aol.com',
  },
  {
    id: '3',
    name: 'Dennis',
    email: 'dennis@aol.com',
  },
];

// Create a "db" with an user model and some defaults
export const db = factory({
  user: {
    id: primaryKey(),
    name: () => 'Firstname',
    email: () => 'email@email.com',
  },
});

// create 3 users
mockUsers.forEach((user) => db.user.create(user));

```

Here we created some fake/mock data and then made use of MSW's `data` library to create an in-memory database. This will allow us to read & change data while developing/testing our app, almost as if we were interacting with a real DB.

`src/mocks/server.js`

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

// for node/test environments
export const server = setupServer(...db.user.toHandlers('rest', 'http://localhost:8000/api/'));

```

`src/mocks/browser.js`

```javascript
import { setupWorker } from 'msw';
import { db } from './db';

// for browser environments
export const worker = setupWorker(...db.user.toHandlers('rest', 'http://localhost:8000/api/'));

```

Then we also create 2 request handlers that will intercept any call to the specified URL. A worker for browser environments which can be used in Browser tests (e.g. Cypress) or during development in general. And one server for node environments which will be used in our Testing Library tests.

We also make use of the `toHandlers()` utility which takes a DB model, User in this case, and creates all the handlers for the usual CRUD operations automagically. This does exactly match our real API's specifications. What a lucky coincidence!

With that in place we can connect it to our app & test runner.

For tests we can use `src/setupTests.js`:

```javascript
import '@testing-library/jest-dom';
import { server } from './mocks/server.js';

// Establish API mocking before all tests.
beforeAll(() => server.listen());
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());
// Clean up after the tests are finished.
afterAll(() => server.close());
```

For our browser environments we call `worker.start` as soon as possible in `src/App.js`:

```diff
import { QueryClient, QueryClientProvider } from 'react-query';
import { Users } from './Users';


+ if (process.env.NODE_ENV === 'development') {
+  const { worker } = require('./mocks/browser');
+  worker.start();
+ }

const queryClient = new QueryClient();
```

Now any matching call `http://localhost:8000/api/*`, our imaginary API, will be intercepted and mock data will be returned - in tests AND in the real app if we would start the development server with `yarn start`.

## First test

We have set up the base of our app and configured MSW. This would be a good time to start and actually develop our UI. For that we will write a test first. It will fail (🔴) at first and we will implement the actual code to make it pass (🟢) afterwards. That will be the flow we will use to implement all the following features as well.

From now on we can leave `yarn test` and `yarn start` running in parallel to watch our tests and develop our app in the browser.

Let's assume our users list will have a loading state while loading users.

Users.test.js
```javascript
import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Users } from './Users';

describe('Users', () => {
  test('renders loading', async () => {
    const queryClient = new QueryClient();
    render(
      <QueryClientProvider client={queryClient}>
        <Users />
      </QueryClientProvider>
    );
    await waitFor(() => {
      expect(screen.getByText('Loading Users...')).toBeInTheDocument();
    });
  });
});
```

Our test fails (🔴) with `Unable to find an element with the text: Loading Users....` as expected. Now we try to get it to pass.

In `src/Users.js` we make use of `useQuery` and a `fetch` helper function `getUsers` to call our users API endpoint at `/api/users`. Eventually we handle the `isLoading` state.

```javascript
import { useQuery } from 'react-query';

async function getUsers() {
  try {
    const data = await fetch(`http://localhost:8000/api/users`);
    if (!data.ok) {
      throw new Error(data.status);
    }
    const json = await data.json();
    return json;
  } catch (error) {
    console.log(error);
  }
}

export function Users() {
  const { isLoading } = useQuery('users', getUsers);

  if (isLoading) {
    return <div>Loading Users...</div>;
  }
  return <div>Users</div>;
}
```

Our tests should pass now (🟢).

![Screenshot 2021-05-01 at 13.40.44](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fwcff0v2l9t0dk5z37i6.png)

Next feature is actually showing the list of users. Again, we write our test first.
 

In `Users.test.js` we expect the names of all our mock users to be displayed.

```javascript
import { mockUsers } from './mocks/db';

...


test('lists users', async () => {
    const queryClient = new QueryClient();
    render(
      <QueryClientProvider client={queryClient}>
        <Users />
      </QueryClientProvider>
    );

    await waitFor(() => {
      mockUsers.forEach((mockUser) => {
        expect(screen.getByText(mockUser.name, { exact: false })).toBeInTheDocument();
      });
    });
 });
```

It fails (🔴) and we implement the correct code to make it pass.

```javascript
export function Users() {
  const { isLoading, data: users } = useQuery('users', getUsers);

  if (isLoading) {
    return <div>Loading Users...</div>;
  }

  return (
    <>
      <div>Users</div>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            <div>Name: {user.name}</div>
          </li>
        ))}
      </ul>
    </>
  );
}
```

Tests pass (🟢) and we can go on implement the next feature.

Our app should have the functionality for creating users as well. You know the drill: failing test first!

Users.test.js
```javascript
test('create new user', async () => {
    const queryClient = new QueryClient();
    render(
      <QueryClientProvider client={queryClient}>
        <Users />
      </QueryClientProvider>
    );

    const createButton = await screen.findByText('Create new User');

    fireEvent.click(createButton);

    const newUserInList = await screen.findByText('Name: John');
    expect(newUserInList).toBeInTheDocument();
  });
```

And the matching implementation. We create a new component `CreateUser`.

```javascript
import { useMutation, useQueryClient } from 'react-query';

async function createUser(newUser) {
  try {
    const data = await fetch(`http://localhost:8000/api/users`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(newUser),
    });
    if (!data.ok) {
      throw new Error(data.status);
    }
    const json = await data.json();
    return json;
  } catch (error) {
    console.log(error);
  }
}

export function CreateUser() {
  const queryClient = useQueryClient();
  const createUserMutation = useMutation((newUser) => createUser(newUser), {
    onSuccess: () => {
      queryClient.invalidateQueries('users');
    },
  });

  return (
    <button
      onClick={() =>
        createUserMutation.mutate({
          id: '4',
          name: 'John',
          email: 'john@aol.com',
        })
      }
    >
      Create new User
    </button>
  );
}
```

We use React-Query's `useMutation` and a helper function `createUser` to make a POST call to our API. `onSuccess` we invalidate our `users` data to trigger a refetch. For simplicity we hard code the new users info.

Our test passes (🟢).

At this point I think it is clear how a possible workflow could look like and what the possibilities & advantages of having a mocked interactive API are. Our UI is ready to be connected to a real API once it is implemented.

I won't go through testing all the other features here but instead link to a repository with the completed code in place.

Or maybe you want to take it as a challenge and complete the rest of the tests yourself? Here are some ideas that should probably be implemented next:

- We are still missing "Show a user's detailed info", "Updating a user" and "Deleting a user"
- What about Error handling and states?
- Another thing that already stands out is that there could be a lot of repetition with the fetch helper functions. Maybe refactor and find a better abstraction for it?


Repository: : https://github.com/DennisKo/react-msw-demo

I am open for questions and improvements! Contact me here or on Twitter:

🐦 https://twitter.com/DennisKortsch