Writing an integration test with React Testing Library
Tags: webdev, react, testing, javascript
Published: 2020-05-25 12:49:07

*This post was originally published in [my personal site](https://francodalessio.com/writing-integration-test-react-testing-library/)*.

At this point I think I don't have to explain the **importance of testing** your applications. Automated tests allow you to **catch bugs while still developing** and avoid getting a call at 2am because your application is not working as expected.

It's all about **confidence**. When shipping new code, you want that confidence. You want to _know_ that your application will work just fine. Of course you can't know, but if you have **a bunch of great tests**, you will be a lot more confident.

Yes, you need to **invest time** writing tests. But in the long run, it will **save you a lot of time** and will avoid you a lot of headaches.

## But can you have _too many tests_?

Nobody argues that testing applications is a waste of time anymore. But we still have this problem of writing **too many tests**. It's a big challenge to understand **what should be tested**, and also **how to test it** in order to get as much confidence as possible.

A while back, [Guillermo Rauch‏](https://twitter.com/rauchg) tweeted this:

{% twitter 807626710350839808 %}

Many times we hear that our tests should cover 100% of our code. And that's simply not always a good idea. There is always a point that is the **turning point**. Once you get that X% of coverage, **the new tests you write are not really helping**. The number is different for every project, but it's never as high as 100%.

When you go over that point, you start spending time **testing things that really don't need to be tested**. You test things that have **no logic at all**. You have one line of code that is uncovered and you write awful tests, usually involving **implementation details**, just to get that 100% coverage.

You may not see the issue right away, but as you start to have more and more tests like that, it becomes **hard to mantain them** and ultimately **slow the whole team down**. The important thing to have in mind here is that **you should write tests to have more confidence** that your application is working fine. And these kind of tests doesn't help to increase that confidence at all.

## Ok, and why integration tests?

The three most common types of tests are: **unit, integration and end to end.**

**Unit tests** are **faster** to write and of course, **cheaper**. But they also **don't give you a lot of confidence**. It's fine testing if component `A` renders correctly, but if you don't _also_ test it together with `B` and `C`, you won't have much confidence in your application.

**End to end** tests give you **a lot of confidence**, because you are testing everything. But they also take **a lot of time to write**, and are of course **the most expensive option**.

**Integration** tests are the **perfect balance**. You test **how your components work together**, but mock some things (for example, HTTP requests) so it **doesn't take you a lot of time** to write them. And they give you **a lot of confidence** as well. Once you start testing your components this way, you'll see that you may not need a lot of your unit tests at all.

This is why it's a great idea to **spend _most_ of your time writing integration tests**.

This doesn't mean that you should _only_ write this kind of tests.

This also doesn't mean that unit and end to end tests are useless.

It just means that it's intelligent to spend _most_ of your time writing integration tests, because they provide you with **a great balance on the trade-offs between confidence and effort**.

### A quick note on mocking

When you write integration tests, you want to **mock as less as possible**. Mocking stuff reduces the confidence in that test. So just keep that in mind.

## Have you heard of React Testing Library?

[React Testing Library](https://testing-library.com/docs/react-testing-library/intro) is by far the **best** and **most popular testing library** available for React right now.

Its creator, [Kent C. Dodds](https://kentcdodds.com/), wrote it with this in mind:

> "The more your tests resemble the way your software is used, the more confidence they can give you."

He's also a big advocate of integration tests, so let's use this library to write one!

## First we need an application

I wrote a small app that consists of two pages. In the home page, you can write a `postId`. The `Submit` button is disabled until you write something.

![The Home page of our application](https://dev-to-uploads.s3.amazonaws.com/i/dcbi3dzbh8dkdqnm6nzz.png)

Once you write a `postId` and press the `Submit` button, you navigate to a second page, `/post/:postId`.

When you land on this page, you will first see a `Loading...` message:

![The loading message in the Post page](https://dev-to-uploads.s3.amazonaws.com/i/xqj9ly99tx3o7rq5p3bm.png)

An HTTP request is fired to the [JSON Placeholder API ](https://jsonplaceholder.typicode.com/) using the provided `postId`, and once the data is received, the post is displayed. This page also has a link to go back to the Home page.

![The post data is fetched from an API an displayed once it's ready](https://dev-to-uploads.s3.amazonaws.com/i/soelscxsth6nrtgqwm3d.png)

Here is the full code:

```jsx
import React from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import { fetchPost } from "./api";

export default function App2() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route exact path="/post/:postId" component={Post} />
      </Switch>
    </Router>
  );
}

function Home({ history }) {
  const [postId, setPostId] = React.useState("");
  return (
    <div>
      <h1>Welcome!</h1>
      <h2>Search for a post by its ID</h2>

      <label htmlFor="postId">Post ID: </label>
      <input
        id="postId"
        value={postId}
        onChange={e => setPostId(e.target.value)}
      />
      <button
        disabled={!postId}
        onClick={() => history.push(`/post/${postId}`)}
      >
        Submit
      </button>
    </div>
  );
}

function Post({ match }) {
  const { postId } = match.params;
  const [post, setPost] = React.useState();
  React.useEffect(() => {
    (async function fetch() {
      setPost(await fetchPost(postId));
    })();
  }, [postId]);
  return (
    <div>
      <h1>Post {postId}</h1>
      {!post ? (
        <p>Loading...</p>
      ) : (
        <>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </>
      )}
      <Link to="/">Back to Home</Link>
    </div>
  );
}
```

And this is the `api.js` file:

```javascript
export const fetchPost = async postId => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  return response.json();
};
```

You can play with the application in [this code sandbox](https://codesandbox.io/s/search-post-by-id-62jwh).

## Now we're ready for the test!

I won't write about configurations in this post. **I'm assuming you have Jest and React Testing Library set up** and you're ready to write your test.

I will write each step without repeating any code, and then **at the end I'll leave the complete test snippet** for you to check.

Let's start with a few imports. Of course we need to import `React` first, and we also need `render` and `screen` from React Testing Library:

```javascript
import React from "react";
import { render, screen } from "@testing-library/react";
```

`render` allows us to render our component in order to start testing it, and `screen` provides us with all the **queries** we need to get elements.

We now need to create our test:

```javascript
test("Can search for a post using its ID", async () => {});
```

We import our `App` component and call the `render` function.

```javascript
import App from "../app";

test("Can search for a post using its ID", async () => {
  render(<App />);
});
```

Great! Our test should be passing. Now we can start using `screen` to check if our component is displaying what it should.

### The Home page

We're in the Home page, so we should see a _Welcome_ message. As this is is text, we can use `getByText` query. We get that element and then wrap it in an `expect` statement, _expecting_ that it exists in the document.

```javascript
expect(screen.getByText(/welcome/i)).toBeInTheDocument();
```

It's always a good idea to use **regular expressions** for your text content, and **ignore cases**. First, your users doesn't care about text cases. And second, if it's a phrase, you may slightly **change** your words in the future, but you'll always keep a few **important** ones. Your test should be looking for them, as they will always be there.

Let's see an example of that. In our home page we have an `h2` element that says `Search for a post by its ID`. We could totally do this, and it would work:

```javascript
expect(screen.getByText("Search for a post by its ID")).toBeInTheDocument();
```

But what if the next week we change that phrase to `Here you can search for a post. The only thing you need is its ID`? Of course, now our test **would be broken**! A better way to write this statement would be this one:

```javascript
expect(screen.getByText(/search.*post.*id/i)).toBeInTheDocument();
```

That's perfect! We know that **we have three important words that will always be there** (`search`, `post` and `id`). With this statement, the test wouldn't break if we changed our phrase as we said before.

The last thing we need to check in our Home page is the `Submit` button, which should be there but be **disabled**.

```javascript
expect(screen.getByText(/submit/i)).toBeDisabled();
```

### Searching for a post

Our Home page is rendering correctly and has all the user needs to search for a post. We need to **simulate the user typing** in our `input` field, and React Testing Library has our backs.

We need to import the `user-event` module:

```javascript
import user from "@testing-library/user-event";
```

But before we can simulate the user typing in the `input` field, we need to **get** that element. A good practice when testing forms is to **get elements by their `label`**. This way we can also check if the `label` and the `input` field are associated correctly, which is important for **accessibility**.

So let's use the `getByLabelText` query to get that input:

```javascript
screen.getByLabelText(/post id/i);
```

And now we are ready to simulate the user typing in it, using the `user-event` module:

```javascript
user.type(screen.getByLabelText(/post id/i), "1");
```

Great! To finish the user interaction, we need to **click** the `Submit` button, which we expect to be **enabled** now.

```javascript
const submitButton = screen.getByText(/submit/i);
expect(submitButton).toBeEnabled();
user.click(submitButton);
```

### Landing on the Post page

Now that we clicked the submit button, we should **navigate** to the `Post` page. The first thing we should see there is a `Loading...` message, so let's get that.

```javascript
screen.getByText(/loading/i);
```

But if you write that, you'll see that **the test won't pass**:

![The test fails when we try to use getByText query](https://dev-to-uploads.s3.amazonaws.com/i/9dn3bbhi16avnx3kx8fi.png)

When this happens, we need to **use `find*` queries, along with `await`**. The test will then _wait_ until the loading message shows up.

```javascript
await screen.findByText(/loading/i);
```

Perfect! Now the test is passing.

### Mocking HTTP requests

The next thing that happens in our application is that **an HTTP request is fired to the API** to get the post details. We don't want to fire an actual request in our test, so we need to **mock** it.

First of all, right after our imports, let's mock the `api` module using `jest`:

```javascript
jest.mock("../api");
```

And now we can import the module as a **mocked** version of it:

```javascript
import { fetchPost as mockFetchPost } from "../api";
```

In our test, let's create a **mock post**, the object that our fake request will return when resolved:

```javascript
const mockPost = {
  id: "1",
  title: "Post Title",
  body: "Post Body",
};
```

And then instruct our mock function to **return that object** when called:

```javascript
mockFetchPost.mockResolvedValueOnce(mockPost);
```

We can also make a small enhancement to use this mock post object when we simulate the user typing in the input field:

```javascript
user.type(screen.getByLabelText(/post id/i), mockPost.id);
```

Perfect! Everything is set up and we can now continue with our test.

### Wrapping up our test

First of all we should check that our mock function is called, and that **it's called just once**:

```javascript
expect(mockFetchPost).toHaveBeenCalledTimes(1);
```

And also that it is called with the **mock post id** we defined:

```javascript
expect(mockFetchPost).toHaveBeenCalledWith(mockPost.id);
```

Now let's check that the actual **post information is displayed** on the screen for the user:

```javascript
expect(screen.getByText(mockPost.title)).toBeInTheDocument();
expect(screen.getByText(mockPost.body)).toBeInTheDocument();
```

The only thing left to test is the **link back home**. First we click it:

```javascript
user.click(screen.getByText(/back.*home/i));
```

And then we should be in the Home page:

```javascript
await screen.findByText(/welcome/i);
```

We're done! This is the complete test:

```javascript
import React from "react";
import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";
import { fetchPost as mockFetchPost } from "../api";
import App from "../app";

jest.mock("../api");

test("Can search for a post using its ID", async () => {
  const mockPost = {
    id: "1",
    title: "Post Title",
    body: "Post Body",
  };
  mockFetchPost.mockResolvedValueOnce(mockPost);
  render(<App />);

  expect(screen.getByText(/submit/i)).toBeDisabled();
  expect(screen.getByText(/welcome/i)).toBeInTheDocument();
  expect(screen.getByText(/search.*post.*id/i)).toBeInTheDocument();

  user.type(screen.getByLabelText(/post id/i), mockPost.id);
  const submitButton = screen.getByText(/submit/i);
  expect(submitButton).toBeEnabled();
  user.click(submitButton);

  await screen.findByText(/loading/i);
  expect(mockFetchPost).toHaveBeenCalledWith(mockPost.id);
  expect(mockFetchPost).toHaveBeenCalledTimes(1);
  expect(screen.getByText(mockPost.title)).toBeInTheDocument();
  expect(screen.getByText(mockPost.body)).toBeInTheDocument();

  user.click(screen.getByText(/back.*home/i));
  await screen.findByText(/welcome/i);
});
```

## That's it!

We tested the **_happy path_** of **searching** for a post, **navigating** to a different page, **waiting** for it to load, **seeing** the post information on the screen and **navigating** back to the home page. It was easy, wasn't it?

Of course there are **more things to test**, even in this small application. I hope this was useful and that you **start writing a lot of long, integration tests** for your applications.