Testing Next.js apps with Jest
Tags: nextjs
Published: 2022-04-08 17:00:01

**Written by [Atharva Deosthale](https://blog.logrocket.com/author/atharvadeosthale/)✏️**

As a developer, you know how important tests are for any production-level project. Writing tests takes some time, but they will help you in the long run to solve problems in the codebase. You can also integrate these tests into GitHub Actions, so that whenever you deploy to production or someone makes a pull request, tests will run automatically and you'll be notified of any failed tests.

[Jest](https://blog.logrocket.com/testing-with-jest-from-zero-to-hero-85ce0e9cc953/) is an amazing, inbuilt tool to test React apps. And since [Next.js released v12](https://blog.logrocket.com/whats-new-next-js-12/), it has Jest configuration inbuilt as well, powered by the Rust compiler (a highlight of the Next v12 update).

In this tutorial, we will learn how to set up Jest with Next by writing a simple test for a calculator app. We will also see what happens when we simulate a test failure, so you can decide whether or not Jest is efficient enough for your own project.

### Contents

*   [Tutorial prerequisites](#prerequisites)
*   [Creating a new Next.js app](#creating-a-new-nextjs-app)
*   [Building the calculator](#creating-a-calculator)
*   [Setting up Jest](#setting-up-jest)
*   [Testing the Next.js calculator app with Jest](#testing-the-nextjs-calculator-app-with-jest)
*   [Testing calculator operations and simulating an error](#testing-calculator-operations-and-simulating-an-error)
*   [Conclusion](#conclusion)

### Prerequisites

*   Working knowledge of React and Next.js
*   Working knowledge of how to test applications
*   A code editor — I prefer Visual Studio Code
*   Node.js installed on your machine

If you get stuck somewhere in the tutorial, feel free to refer to the [GitHub repository](https://github.com/atharvadeosthale/jest-tutorial-article).

## Creating a new Next.js app

Navigate to a safe directory and enter the following command in the terminal to set up your new [Next application](https://blog.logrocket.com/testing-error-handling-patterns-next-js/):

```shell
npx create-next-app jest-tutorial
```

You can replace `jest-tutorial` with any other name for the application. Once the installation is complete, open the project in your code editor and run the following command in the terminal to fire up the development server:

```shell
npm run dev
```

## Creating a calculator

Because we are focusing specifically on testing here, we won’t be covering on how our calculator works. However, for us to test, here is the code for the calculator.

The contents of `index.js` under the `pages` directory is as follows:

```js
import Head from "next/head";
import Image from "next/image";
import { useState } from "react";
import styles from "../styles/Home.module.css";
export default function Home() {
  const [num1, setNum1] = useState(0);
  const [num2, setNum2] = useState(0);
  const [result, setResult] = useState(0);
  const add = () => {
    setResult(parseInt(num1) + parseInt(num2));
  };
  const subtract = () => {
    setResult(parseInt(num1) - parseInt(num2));
  };
  const multiply = () => {
    setResult(parseInt(num1) * parseInt(num2));
  };
  const divide = () => {
    setResult(parseInt(num1) / parseInt(num2));
  };
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className={styles.result} data-testid="result">
        {result}
      </div>
      <input
        type="number"
        className={styles.input}
        data-testid="num1"
        value={num1}
        onChange={(e) => setNum1(e.target.value)}
      />
      <input
        type="number"
        className={styles.input}
        data-testid="num2"
        value={num2}
        onChange={(e) => setNum2(e.target.value)}
      />
      <button onClick={add} className={styles.button} data-testid="add">
        Add
      </button>
      <button
        onClick={subtract}
        className={styles.button}
        data-testid="subtract"
      >
        Subtract
      </button>
      <button
        onClick={multiply}
        className={styles.button}
        data-testid="multiply"
      >
        Multiply
      </button>
      <button onClick={divide} className={styles.button} data-testid="divide">
        Divide
      </button>
    </div>
  );
}
```

One thing to note about in the above code is the `data-testid` attribute added to elements, such as a result area, input fields, and buttons. This ID helps us identify the specific field in our test files and perform actions on them while testing. These IDs won’t make any difference in production.

Here are the contents for `Home.module.css` under the `styles` directory:

```css
.container {
  padding: 0 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: #121212;
  color: white;
}
.result {
  font-size: 4rem;
  font-weight: bold;
  margin-bottom: 1rem;
}
.input {
  margin: 0.5rem 0;
  padding: 0.5rem;
  font-size: large;
  width: 13rem;
  background-color: #121212;
  border: 1px solid #525252;
  color: white;
  border-radius: 10px;
}
.button {
  font-size: large;
  padding: 0.5rem;
  width: 13rem;
  margin: 0.5rem 0;
  border: 1px solid black;
  background-color: black;
  border-radius: 10px;
  color: white;
}
```

Now when you visit `http://localhost:3000`, you should see our calculator up and running:

![calculator app](https://blog.logrocket.com/wp-content/uploads/2022/03/calculator-app.png)

Now, let’s move on to testing this calculator using Jest!

## Setting up Jest

To set up Jest, we need to install a few required packages. Run the following command in the terminal to install them:

```shell
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
```

Remember, we are using `--save-dev` here, which means these packages are included in our `devDependencies` and won’t be used in production.

Now, let’s create a new file to store our Jest configuration. In the project root folder, create a new file named `jest.config.js` with the following configuration (these configurations are from the official Next [documentation](https://nextjs.org/docs/testing)):

```js
const nextJest = require("next/jest");
const createJestConfig = nextJest({
  dir: "./",
});
const customJestConfig = {
  moduleDirectories: ["node_modules", "<rootDir>/"],
  testEnvironment: "jest-environment-jsdom",
};
module.exports = createJestConfig(customJestConfig);
```

Now, go to `package.json` and add a script called `test`, which runs the command `jest` `--watch`.

Your scripts should look like this:

```js
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint",
  "test": "jest --watch"
},
```

Now that our configurations are ready, we can move forward with writing tests.

## Testing the Next.js calculator app with Jest

In the project root directory, create a new folder called `tests`, which will be used by Jest to look up tests. Then, create a new file called `index.test.js`.

Firstly, let’s import some dependencies:

```js
import Home from "../pages/index";
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
```

We are importing the `Home` component directly from our Next app because we will be rendering it during the test.

Now, let’s write a test to see if all elements are rendering correctly:

```js
describe("Calculator", () => {
  it("renders a calculator", () => {
    render(<Home />);
    // check if all components are rendered
    expect(screen.getByTestId("result")).toBeInTheDocument();
    expect(screen.getByTestId("num1")).toBeInTheDocument();
    expect(screen.getByTestId("num2")).toBeInTheDocument();
    expect(screen.getByTestId("add")).toBeInTheDocument();
    expect(screen.getByTestId("subtract")).toBeInTheDocument();
    expect(screen.getByTestId("multiply")).toBeInTheDocument();
    expect(screen.getByTestId("divide")).toBeInTheDocument();
  });
});
```

These tests are generally human-readable. Let’s see what’s happening in the above code:

First, the `describe` function describes what the module is. You can give it any name you want; the name will be displayed in the console when the tests are run.

Next, the `it` function specifies an individual test. You can have multiple `it`s within `describe`.

We are then rendering out the `<Home/>` component, which we imported from `index.js` under the `pages` directory. This will simulate a DOM and render the component on it.

The following `expect` function checks whether a condition is `true` or `false`. Its outcome pretty much tells you the result of the test. If any one of these `expect` statements are `false`, the test will fail and you will see an error message in the console.

Finally, we are using `toBeInTheDocument()` to check if the element actually exists in the DOM created by Jest. We provided test IDs in the page, so it’s easier for Jest to identify these elements.

Now, you can run the test. Use the following command in your terminal:

```shell
npm run test
```

You should see a test result like this:

```shell
 PASS  __tests__/index.test.js
  Calculator
    ✓ renders a calculator (15 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.554 s, estimated 1 s
Ran all test suites.

Watch Usage: Press w to show more.
```

Once you run the test command, it automatically reruns the test once any file in the project is saved, so we don’t need to kill and restart the test terminal.

## Testing calculator operations and simulating an error

Now, let’s create a test for checking if the numbers add properly:

```js
it("adds numbers", () => {
  render(<Home />);
  // check if adds properly
  const num1input = screen.getByTestId("num1");
  const num2input = screen.getByTestId("num2");
  const addButton = screen.getByTestId("add");
  const resultArea = screen.getByTestId("result");
  fireEvent.change(num1input, { target: { value: 5 } });
  fireEvent.change(num2input, { target: { value: 8 } });
  addButton.click();
  expect(resultArea).toHaveTextContent("13");
});
```

In the above code, we are simulating typing and pressing a button in the calculator. We are also providing two numbers to add, and checking if the result the calculator provides is the same.

Once you save the file, check the terminal and your test should automatically rerun and provide the following output:

```shell
 PASS  __tests__/index.test.js
  Calculator
    ✓ renders a calculator (18 ms)
    ✓ adds numbers (9 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.526 s, estimated 1 s
Ran all test suites.

Watch Usage: Press w to show more.
```

Perfect! Now, let’s try to simulate an error in the application. First, we will make a change in the actual calculator application so that it does not work as expected on purpose. To do so, change the `add` function in `index.js` as follows:

```js
const add = () => {
  setResult(parseInt(num1) - parseInt(num2));
};
```

Here we changed the function so that it subtracts instead of adds. When you save the file, you should immediately see an error:

```js
 FAIL  __tests__/index.test.js
  Calculator
    ✓ renders a calculator (14 ms)
    ✕ adds numbers (13 ms)

  ● Calculator › adds numbers

    expect(element).toHaveTextContent()

    Expected element to have text content:
      13
    Received:
      -3

      28 |     addButton.click();
      29 |
    > 30 |     expect(resultArea).toHaveTextContent("13");
         |                        ^
      31 |   });
      32 | });
      33 |

      at Object.<anonymous> (__tests__/index.test.js:30:24)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.667 s, estimated 1 s
Ran all test suites.

Watch Usage: Press w to show more.
```

Jest tried to add numbers and didn’t see a number we expected, which means there is some problem in the `add` function. That’s how you can check problems in your code when a test fails. Revert the changes you made in `index.js` file so that the contents are back to the original working order.

Now, let’s add a few more tests to check if all the operations are working well. Here’s the entire code for `index.test.js`:

```js
import Home from "../pages/index";
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
describe("Calculator", () => {
  it("renders a calculator", () => {
    render(<Home />);
    // check if all components are rendered
    expect(screen.getByTestId("result")).toBeInTheDocument();
    expect(screen.getByTestId("num1")).toBeInTheDocument();
    expect(screen.getByTestId("num2")).toBeInTheDocument();
    expect(screen.getByTestId("add")).toBeInTheDocument();
    expect(screen.getByTestId("subtract")).toBeInTheDocument();
    expect(screen.getByTestId("multiply")).toBeInTheDocument();
    expect(screen.getByTestId("divide")).toBeInTheDocument();
  });
  it("adds numbers", () => {
    render(<Home />);
    // check if adds properly
    const num1input = screen.getByTestId("num1");
    const num2input = screen.getByTestId("num2");
    const addButton = screen.getByTestId("add");
    const resultArea = screen.getByTestId("result");
    fireEvent.change(num1input, { target: { value: 5 } });
    fireEvent.change(num2input, { target: { value: 8 } });
    addButton.click();
    expect(resultArea).toHaveTextContent("13");
  });
  it("subtracts numbers", () => {
    render(<Home />);
    // check if adds properly
    const num1input = screen.getByTestId("num1");
    const num2input = screen.getByTestId("num2");
    const subtractButton = screen.getByTestId("subtract");
    const resultArea = screen.getByTestId("result");
    fireEvent.change(num1input, { target: { value: 8 } });
    fireEvent.change(num2input, { target: { value: 5 } });
    subtractButton.click();
    expect(resultArea).toHaveTextContent("3");
  });
  it("multiplies numbers", () => {
    render(<Home />);
    // check if adds properly
    const num1input = screen.getByTestId("num1");
    const num2input = screen.getByTestId("num2");
    const multiplyButton = screen.getByTestId("multiply");
    const resultArea = screen.getByTestId("result");
    fireEvent.change(num1input, { target: { value: 5 } });
    fireEvent.change(num2input, { target: { value: 8 } });
    multiplyButton.click();
    expect(resultArea).toHaveTextContent("40");
  });
  it("divides numbers", () => {
    render(<Home />);
    // check if adds properly
    const num1input = screen.getByTestId("num1");
    const num2input = screen.getByTestId("num2");
    const divideButton = screen.getByTestId("divide");
    const resultArea = screen.getByTestId("result");
    fireEvent.change(num1input, { target: { value: 20 } });
    fireEvent.change(num2input, { target: { value: 2 } });
    divideButton.click();
    expect(resultArea).toHaveTextContent("10");
  });
});
```

Once you save the file, you should see all the tests passing like so:

```js
 PASS  __tests__/index.test.js
  Calculator
    ✓ renders a calculator (14 ms)
    ✓ adds numbers (6 ms)
    ✓ subtracts numbers (4 ms)
    ✓ multiplies numbers (4 ms)
    ✓ divides numbers (4 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.674 s, estimated 1 s
Ran all test suites.

Watch Usage: Press w to show more.
```

## Conclusion

After reading this article, I hope you learned how important testing is in production-level projects. Especially with huge projects, it would take a lot of time to manually test them. So, writing automated tests with Jest is a great solution. Every time you ship a feature and something breaks, you will be notified when a test fails so that you can work on a fix immediately.

---

## [LogRocket](https://logrocket.com/signup): Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try [LogRocket](https://logrocket.com/signup). 

![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)

[LogRocket](https://logrocket.com/signup) is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — [start monitoring for free](https://logrocket.com/signup).