DEV Community

Adeku Ali
Adeku Ali

Posted on

Guide to writing integration tests in express js with Jest and Supertest

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.

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


    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();
      }
    });
Enter fullscreen mode Exit fullscreen mode

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

  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
Enter fullscreen mode Exit fullscreen mode

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

test/integration/tasksController.test.js

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");
        });
      });
    });
Enter fullscreen mode Exit fullscreen mode

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
Supertest

Top comments (0)