Guide to writing integration tests in express js with Jest and Supertest
Tags: javascript, express, node
Published: 2023-03-07 08:22:44

Writing integration tests for new developers can prove a little tricky. During my first foray into writing integration tests, it took a while to understand how to write tests for express applications effectively.

While unit testing is a way of testing a unit - the smallest piece of code that can be logically isolated in a system, integration testing is testing all solid features that make up the application. 

In this article, I illustrate how to write integration tests for a task manager API built with express.js.


## Repository

Check out the complete source code [here](https://github.com/Adeku5080/task-manager-with-express).

## Prerequisites

- Basic Knowledge of Javascript
- Node installed on your device


## Setting up your Test Folder Structure

In the root directory of your application, create a folder named `test`; in your test folder, create a folder called `integration` , and this folder should contain all your integration test files.

Integration test files should be named like this `.spec.js` or `.test.js`, so your test filenames should look like `foo.spec.js` or `foo.test.js`


## Configuring Jest

You are going to make use of Jest as the testing framework. Jest is a JavaScript testing framework designed to ensure the correctness of any JavaScript codebase. It allows you to write tests with an approachable, familiar, and feature-rich API that gives you results quickly.

 `npm install jest --save-dev` 
 
 In the  `package.json`  file, add a `test` script with the following command:
 
`"test": "cross-env NODE_ENV=test jest --testTimeout=5000 --detectOpenHandles"` 
 
 Jest runs in a browser-like environment using `jsdom` by default, but since this is a node application,  a node-like environment is specified instead.
 
`   "jest": {"test environment": "node"}
`     
  

## Creating a Mock Database

You can create a mock database to avoid writing tests that interfere with the database. For this article, since MongoDB is used as the database, you will use MongoMemoryServer as your mock database.

MongoMemoryServer will spin up a real MongoDB server and allow storing test data in memory.

`npm i mongodb-memory-server --save-dev` 

In your test folder, create a `boostrap.js` file; this should contain your mock database configuration.


`test/bootstrap.js`

```javascript

    const mongoose = require("mongoose");
    const { MongoMemoryServer } = require("mongodb-memory-server");
    
    let mongod;
    
    beforeAll(async () => {
      mongod = await MongoMemoryServer.create();
      const uri = mongod.getUri();
      await mongoose.connect(uri);
    });
    afterAll(async () => {
      await mongoose.connection.dropDatabase();
      await mongoose.connection.close();
      await mongod.stop();
    });
    afterEach(async () => {
      const collections = mongoose.connection.collections;
      for (const key in collections) {
        const collection = collections[key];g,
        await collection.deleteMany();
      }
    });
```



## Writing The Tests

To test the API endpoints of the application, you will import the instance of the application, the `supertest` module, and the task Model. The `app.js` file looks like this :

 `npm i supertest` 

`app.js`


  

```javascript
  const express = require('express')
    const taskRouter = require("./routes/task");
    const notFound = require("./middlewares/not-found")
    const errorHandler = require("./middlewares/error-handler")
    const app = express()
    
    const morgan = require('morgan')
    
    app.use(express.static("./public"));
    app.use(express.json());
    app.use(morgan('dev'))
    app.use("/api/v1/tasks", taskRouter);
    app.use(notFound)
    
    module.exports = app
```

This file contains the tests for the API routes in the application.


`test/integration/tasksController.test.js`

    

```javascript
const request = require("supertest");
    const app = require("../../app");
    const TaskModel = require("../../models/tasks");
    
    describe("Task Controller", () => {
      describe("create task", () => {
        it("should return 201 and the task created", async () => {
          const response = await request(app)
            .post("/api/v1/tasks")
            .set("content-type", "application/json")
            .send({
              name: "task testing",
              completed: "true",
            });
    
          expect(response.status).toBe(201);
          expect(response.body).toHaveProperty("tasks");
        });
      });
    
      describe("get all tasks", () => {
        it("should return 200 and all tasks", async () => {
          const response = await request(app)
            .get("/api/v1/tasks")
            .set("content-type", "application/json");
    
          expect(response.status).toBe(200);
          expect(response.body).toHaveProperty("tasks");
        });
      });
    
      describe("get a task", () => {
        let task;
        beforeEach(async () => {
          task = await TaskModel.create({
            name: "task testid",
          });
        });
    
        it("should return 200 and a single task", async () => {
          const response = await request(app)
            .get(`/api/v1/tasks/${task.id}`)
            .set("content-type", "application/json");
    
          expect(response.status).toBe(200);
          expect(response.body).toHaveProperty("task");
        });
      });
    
      describe("update a task", () => {
        let task;
        beforeEach(async () => {
          task = await TaskModel.create({
            name: "task2 testid",
          });
        });
    
        it("should return 404 if the task with the id doesnt exist", async () => {
          const taskId = "639c80ef98284bfdf111ad09";
          const response = await request(app).patch(`/api/v1/tasks/${taskId}`);
    
          expect(response.status).toBe(404);
          expect(response.body.msg).toEqual("this task does not exist");
        });
    
    
        it("should return 200 and the updated task", async () => {
          const response = await request(app)
            .patch(`/api/v1/tasks/${task.id}`)
            .send({ name: "newtask" });
    
          expect(response.status).toBe(200);
          expect(response.body).toHaveProperty("task");
        });
      });
    
      describe("delete a task", () => {
        let task;
        beforeEach(async () => {
          task = await TaskModel.create({
            name: "task2 testid",
          });
        });
    
        it("should return 404 if the task with the id doesnt exist", async () => {
          const taskId = "639c80ef98284bfdf111ad09";
          const response = await request(app).delete(`/api/v1/tasks/${taskId}`);
    
          expect(response.status).toBe(404);
    
          expect(response.body.msg).toEqual("this task does not exist");
        });
    
        it("should return 200 and the deleted task", async () => {
          const response = await request(app).delete(`/api/v1/tasks/${task.id}`);
    
          expect(response.status).toBe(200);
          expect(response.body).toHaveProperty("task");
        });
      });
    });
```


## Conclusion

Testing is an art, and the knowledge to write good tests for your application can act as a first line of defence for your application.

In this article, you looked at how to write integration tests for an express application using Jest, Supertest, and mongodb-memory-server, configuring jest, and also creating and configuring your test mock database.


## Resources

[Jest documentation](https://jestjs.io/docs/getting-started)
[Supertest](https://www.testim.io/blog/supertest-how-to-test-apis-like-a-pro/)