How to write tests in full-stack MERN web application
Tags: testing, webdev, beginners, tutorial
Published: 2022-11-01 11:38:39

Only by writing tests can you understand the significance of testing. No coding Bootcamp or course, as far as I know, teaches you how to write tests. Writing tests may not be necessary for demo projects or coursework projects, but it is critical for real-world applications. So, in this **Part 3** of the "[Let's build and deploy a full stack MERN web application](https://dev.to/itsrakesh/lets-build-and-deploy-a-full-stack-mern-web-application-1p5)" series, I'll demonstrate how to write tests for ReactJs components and ExpressJs RESTAPI.

## Let's get started

## What is testing?

During the testing stage of software development, the application's actual outcomes are compared to those that were expected. Before deploying your application, this phase is used to ensure that everything is functioning as it should. Beginner developers frequently believe that writing tests are a time-consuming and unimportant process. But they are ignorant of the fact that testing actually buys you a lot of time in the long run. You can think of testing as a one-time investment.

Software development uses a variety of testing methods, including Unit Testing, Integration Testing, Beta Testing, Regression Testing, and others. We will learn about **Unit Testing** in this lesson and how to create tests for applications built with ReactJs and NodeJs.

## What is Unit Testing?

As the name implies, unit testing involves testing individual pieces of code or an application. For instance, in REST API, we are able to create distinct tests for each endpoint, and in a ReactJs application, we can test each component separately.

Let's begin creating tests for our "Productivity Tracker" application. (If you don't know what this is, read the previous article - [Adding Authentication to full stack MERN web application](https://blog.itsrakesh.co/adding-authentication-to-full-stack-mern-web-application))

## Writing tests in NodeJs application with Jest and SuperTest

>Note: I recommend setting up a separate database for testing purposes. But for this tutorial, it's not necessary.

### Step 1: Separate application and server

First, we have to separate the server from the application because we are using SuperTest to test the application, not the server.

So create an `app.js` file in the root and paste this code.

```javascript
const express = require("express");
const cors = require("cors");

const ActivityRouter = require("./routes/activity.route");
const AuthRouter = require("./routes/auth.route");

const app = express();

/* Telling the application to use the express.json() middleware. This middleware will parse the body of
any request that has a Content-Type of application/json. */
app.use(express.json());

/* Allowing the frontend to access the backend. */
app.use(cors());

/* This is a route handler. It is listening for a GET request to the root route of the application.
When it receives a request, it will send back a response with the string "Hello World!". */
app.get("/", (req, res) => {
  res.send("Hello World!");
});

/* Telling the application to use the ActivityRouter for any requests that start with "/api". */
app.use("/api", ActivityRouter);

/* Telling the application to use the AuthRouter for any requests that start with "/api/auth". */
app.use("/api/auth", AuthRouter);

module.exports = app;
```

... and in `server.js`.

```javascript
const express = require("express");
const mongoose = require("mongoose");

const app = require("./app");

/* Loading the environment variables from the .env file. */
require("dotenv").config();

const PORT = process.env.PORT || 5000;
const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost/todoapiDB";

/* Connecting to the database and then starting the server. */
mongoose
  .connect(MONGODB_URI, { useNewUrlParser: true })
  .then(() => {
    app.listen(PORT, console.log("Server stated on port 5000"));
  })
  .catch((err) => {
    console.log(err);
  });
```

### Step 2: Install packages

To start writing tests, you need three npm packages: `jest`, `supertest`, and `cross-env`.

```bash
npm i --save-dev jest supertest cross-env
```

**jest**: Jest is a framework for testing JavaScript code. Unit testing is its main usage of it.
**supertest**: Using Supertest, we can test endpoints and routes on HTTP servers.
**cross-env**: You can set environmental variables inline within a command using cross-env.

### Step 3: Add test script

Open your `package.json` file and add the test script to the scripts.

```json
"scripts": {
    "test": "cross-env NODE_ENV=test jest --testTimeout=5000",
    "start": "node server.js",
    "dev": "nodemon server.js"
},
```

In this case, `testTimeout` is set to `5000` because it's possible for certain requests to take a while to complete, and `cross-env` is being used to set environment variables and `jest` to run test suites.

### Step 4: Start writing tests

First, create a folder called `tests/` at the application's root, and then create a file there called `activity.test.js`. Jest searches for the folder `tests/` at the project's root when you do `npm run test`. As a result, you must place your test files in the `tests/` folder.

Next, import the `supertest` and `mongoose` packages into the test file.

```javascript
const mongoose = require("mongoose");
const request = require("supertest");
```

Import `dotenv` to load environment variables, and import `app.js` as that is where our application starts.

```javascript
const mongoose = require("mongoose");
const request = require("supertest");

const app = require("../app");

require("dotenv").config();
```

You'll need to connect and disconnect the database before and after each test (because we don't require the database once testing is complete).

```javascript
/* Connecting to the database before each test. */
beforeEach(async () => {
  await mongoose.connect(process.env.MONGODB_URI);
});

/* Closing database connection after each test. */
afterEach(async () => {
  await mongoose.connection.close();
});
```

Now write your first unit test.

```javascript
describe("GET /api/activities", () => {
  it("should get all the activities", async () => {
    const token = await request(app).post("/api/auth/login").send({
      email: process.env.EMAIL,
      password: process.env.PASSWORD,
    });

    const response = await request(app)
      .get("/api/activities")
      .set({
        Authorization: "bearer " + token.body.token,
        "Content-Type": "application/json",
      });

    expect(response.statusCode).toBe(200);
    expect(response.body.length).toBeGreaterThan(0);
  });
});
```

In the above code,

- We use `describe` to describe the unit test. Even though it is not required, it will be useful to identify tests in test results.
- In `it`, we write the actual test code. Write the expected result in the first argument, and then in the second argument, write a callback function that contains the test code.
- In the callback function, the request is sent to the endpoint first, and the expected and actual responses are then compared. The test passes if both answers match, else, it fails.

>Note: Since we have authenticated routes in our application, we need to get the token by logging in to the application and using that token to access the route.

You can write tests for all the endpoints in the same manner.

```javascript
describe("POST /api/activity", () => {
  it("should add an activity to the database", async () => {
    const token = await request(app).post("/api/auth/login").send({
      email: process.env.EMAIL,
      password: process.env.PASSWORD,
    });

    const response = await request(app)
      .post("/api/activity")
      .send({
        name: "Jogging",
        time: "3:00 PM",
      })
      .set({
        Authorization: "bearer " + token.body.token,
        "Content-Type": "application/json",
      });

    expect(response.statusCode).toBe(201);
  });
});
```

Then run `npm run test` to run the test suites (suite - test file).

![test results](https://i.imgur.com/To2e3Ab.png)

## Writing tests in ReactJs application with React Testing Library

In the same way, let's write tests for React applications. For this, we will be using [React Testing Library](https://testing-library.com/).

>The [`@testing-library`](https://www.npmjs.com/org/testing-library) family of packages helps you test UI components in a user-centric way.

### Step 1: Install packages

To start writing tests in React, you need React Testing Library. But these packages come pre-installed with _create-react-app_ (CRA). If you haven't used CRA you can install them like this.

```bash
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
```

### Step 2: Create test files

It's a good practice to create a test file for every component.

We have 2 components that need to be tested - `<App />` and `<Login />`. So create `App.test.js` and `Login.test.js` in the `src/` folder. **But for now create only the `App.test.js` file.**

### Step 3: Start writing tests

Write your first test.

In the `App.jsx` file add the `data-testid={"app-header-heading"}` attribute to the main heading element. Later, while writing tests we can refer to this element with this id.

```html
<h1 data-testid="app-header-heading">Productivity Tracker</h1>
```

Now, open the `App.test.js` file and paste the below code.

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

import App from "./App";

/* This is a test that is testing the App component. 
 * It is testing that the heading is correct. */
describe("App", () => {
  it("should have exact heading", () => {
    /* Rendering the App component. */
    render(<App />);

    /* Getting the element with the test id of "app-header-heading". */
    const mainHeading = screen.getByTestId("app-header-heading");

    /* Checking that the innerHTML of the element with the test id of "app-header-heading" is equal to
    "Productivity Tracker". */
    expect(mainHeading.innerHTML).toBe("Productivity Tracker");
  });
});
```

In the above code,

- We first imported the required packages - `render` and `screen` from `@testing-library/react` and then imported the component that needs to be tested.
- To describe the unit test, we use the same syntax as in express. Most React developers, by convention, put the component name in the first argument. The actual test is written as a callback function in the second argument.
- In `it`, we write the actual test code. Write the expected result in the first argument, and then in the second argument, write a callback function that contains the test code.
- Rest of the code is self-explanatory and you can also understand from the comments. We are expecting the App component should contain a heading called "Productivity Tracker".

This is the general flow of writing a test for a React component:

- Render the component -> Write elements you want to interact with -> Interact with those elements -> Assert that the outcomes are as expected.

Now run the `npm test` command. You will see the results.

![App component test results](https://i.imgur.com/YsivONe.png)

Please refer to official docs to learn more about React Testing Library and write your own tests.

---

As a challenge, try to implement the "Add activity" flow.
In the next article, we will learn some DevOps stuff.

---

Also read,

- Part 1: [Let's build and deploy a full stack MERN web application](https://dev.to/itsrakesh/lets-build-and-deploy-a-full-stack-mern-web-application-1p5)
- Part 2: [Adding Authentication to full stack MERN web application](https://dev.to/itsrakesh/adding-authentication-to-full-stack-mern-web-application-9mm)

Feel free to ask your doubts in the comments.

---

Enjoyed reading this? Follow for more.