Understanding Integration Testing: A Comprehensive Guide

shivam kumarshivam kumar
8 min read

There are multiple tools available for integration testing like jest, mocha, cypress or node also provide inbuilt support of testing . But we learn the traditional approach using Jest and Supertest Supertest is use for testing the api call .

One more important thing to keep in mind , For the integration testing we have to interact with the databases for that there are several option available :

  • In memory database like mongodb-memory-server for mongodb and like that mostly available for all type of databases. it is very lightweight and easy to implement, Very helpful for CI pipeline

  • Test database : we use a separate real database for the testing purpose We prefer to use the In memory database because it is easy and usefull in ci pipeline

Jest Installation

npm install --save-dev jest

Done this is the minimum installation we need to run the test cases , Now let's learn about some conventions

  • Create tests folder ๐Ÿ“‚ in your root directory

  • Inside tests folder we name every file with .test.js suffix ex: math.test.js Example:

your-project/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ math.js
โ”œโ”€โ”€ tests/
โ”‚   โ””โ”€โ”€ math.test.js
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ jest.config.js
โ””โ”€โ”€ babel.config.js (optional)

Now let's write your first test case ๐Ÿงฉ

  • Create add.js file in your src directory (It can be any where in your src)

  • Write this minimal code:

export function add(a, b) {
  return a + b;
}
  • Now create math.test.js in your tests folder and write your first test case:
import { add } from "../src/utils/add.js";

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBe(3);
});

Don't worry we learn all the terms but for the first time setup purpose copy and paste this code to check our test environment

  • Now modify package.json
{
  "type": "module",
  "scripts": {
    "test": "jest" // Add this script
  },
}
  • Now simply run this command in your terminal
npm test

It may successfully executed the test for some user and for some they encountered and error related to import statement the error you get because you may use type module || ESM syntax (import/export) and jest is an old testing tools it doesn't support ESM by default natively . We have to add some extra piece of code in test script

{
  "type": "module",
  "scripts": {
   "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" // Add this script
  },
}
  • Now simply run this command in your terminal
npm test

You will see this type of output:

node --experimental-vm-modules node_modules/jest/bin/jest.js
(node:15204) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS  tests/math.test.js
โˆš adds 1 + 2 to equal 3 (3 ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.615 s
Ran all test suites.

Congratulations ๐ŸŽŠ you have run your first test case successfully

Now before start with Black Box testing lets learn some frequently use technical terms and functions which make the code understanding very easy.

Black Box V/s White Box

Black Box - only we test on input and output, We do not care about internal logic implementation. White Box - Internal logic is visible and also tested the flow of how function is written.

Supertest+jest integration testing = Black Box testing

  • We do not care about how user.login() is implemented internally we only test the API's output , status code and related stuff.

Test Life Cycle In Jest

Hooks/functionsRun WhenPurpose e.g
beforeAll()One before all testDb connect/server start, etc
afterAll()Once after all testDb disconnect/stop server,etc
beforeEach()Before every individual test caseReset data, mock clear, etc.
afterEach()After every individual test caseUseful for cleaning up test data

Jest Key Terms

Just read once when you see the code you will be able to understand all the things

Term / APIDescription
test(name, fn)Single test case
describe()Group of related tests(suits)
expect(actual)Assertion system
.toBe(value)Checks primitive equality (like ===)
.toEqual(obj)Checks object/array deeply
.toBeDefined()Checks that value is not undefined
.toContain(val)Checks array contains value
.toMatch(/regex/)String matches pattern

Supertest Basics

Now let's understand some basics of supertest. Jest is use for writing the test cases, comparing the input output value , run all the test cases or etc. But in integration testing we have to test our endpoint and jest is not able to make http request to get the data from server. So for that reason we use supertest which is an library which allow to make an http requests in test environment.It basically simulate the api call.

Common Methods

  • .post(url), .get(url), .put(), .delete()

  • .send({ ... }) โ†’ to send JSON body

  • .expect(statusCode) โ†’ expected HTTP status

  • .expect('Content-Type', /json/) โ†’ header check

  • .expect(res => {...}) โ†’ custom logic

Now start with coding part but before that setup some configurations

install packages

npm install --save-dev supertest mongodb-memory-server
  • Now create testSetup.js in your tests folder Now we want to run setup.js initially before any other test file load. because when you run npm test the it start running test from test file in random order but we want to run setup.js before any test file load because is contain basics database setup which is necessary in all test we write later.

Configuration to run setup.js first

create jest.config.js in root directory and pate this configuration


export default {
  testEnvironment: 'node',
  setupFilesAfterEnv: ['<rootDir>/tests/testSetup.js'], // Test DB init
};

Now we start setup in testSetup.js

import mongoose from "mongoose";
import { MongoMemoryServer } from "mongodb-memory-server";

let mongo;

//Firstly we get the mongo uri from the MongoMemoryServer instance
// and then we connect to it using mongoose.
beforeAll(async () => {
  mongo = await MongoMemoryServer.create();
  const uri = mongo.getUri();
  await mongoose.connect(uri, {});
});
// After each test, we clear the database
afterEach(async () => {
  const collections = await mongoose.connection.db.collections();
  for (let collection of collections) {
    await collection.deleteMany({});
  }
});
// After all tests, we close the connection to the database
afterAll(async () => {
  await mongoose.connection.close();
  await mongo.stop();
});

go through the above piece of code it is very straight forward to understand.

Export your app from index.js

export const app = express();

Because we use the same app instance to test

Basics

In testing we can use test or it both are allies to each other. If we want to group the multiple test in suites then we use describe.

Testing The Register Route

import request from "supertest";
import { app } from "../src/index.js";

describe("Authentication Tests", () => {
  it("Should register user successfully", async () => {
    // Arrange
    const newUser = {
      email: "test@gmail.com",
      password: "Test@1234",
    };
    // Act
    const response = await request(app)
      .post("/api/v1/users/signup")
      .set("user-agent", "jest-test")// necessary if you are using Arcjet
      .send(newUser);
    // Assert
    expect(response.statusCode).toBe(201);
    expect(response.body).toHaveProperty(
      "message",
      "User created successfully"
    );
    expect(response.body).toHaveProperty("success", true);
  });
});

go through the above piece of code it is very straight forward to understand now .

Output

(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/auth.test.js
Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.059 s, estimated 9 s
Ran all test suites.

If you want more beautiful console output of test then add verbose:true in your jest.config.js

export default {
  testEnvironment: "node",
  setupFilesAfterEnv: ["<rootDir>/tests/testSetup.js"], // Test DB init
  verbose: true,
};

Now you get output like this:

(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/math.test.js (11.205 s)
  โˆš adds 1 + 2 to equal 3 (14 ms)
 PASS  tests/auth.test.js (26.969 s)
  Authentication Tests
  โˆš Should register user successfully (2234 ms)                                  
Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        28.009 s
Ran all test suites.
PS C:\Users\sap74\Desk

You can add as may tests cases you want , let's see one more example

import request from "supertest";
import { app } from "../src/index.js";

describe("Authentication Tests", () => {
// we can also use test in place of it both are alias to each other

  it("Should register user successfully", async () => {
    // Arrange
    const newUser = {
      email: "test@gmail.com",
      password: "Test@1234",
    };
    // Act
    const response = await request(app)
      .post("/api/v1/users/signup")
      .set("user-agent", "jest-test")
      .send(newUser);
    // Assert
    expect(response.statusCode).toBe(201);
    expect(response.body).toHaveProperty(
      "message",
      "User created successfully"
    );
    expect(response.body).toHaveProperty("success", true);
  });
  test("Should return 400 for missing email", async () => {
    // Arrange
    const newUser = {
      password: "Test@1234",
    };
    // Act
    const response = await request(app)
      .post("/api/v1/users/signup")
      .set("user-agent", "jest-test")
      .send(newUser);
    // Asert
    expect(response.statusCode).toBe(400);
    expect(response.body).toHaveProperty(
      "message",
      "Email and password are required"
    );
  });
});

Tips to write test (AAA)

Always think about 3A's for writing test case:

  • Arrange:Prepare the user data (testUser), register before testing login.

  • Act:Send login request using supertest.

  • Assert:Compare input/output according to need

If you don't want to re run test again and again then you can add --watch flag in your test script:

 "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"

By this it re run your test automatically when you write any new testcase or change in any test case.

0
Subscribe to my newsletter

Read articles from shivam kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

shivam kumar
shivam kumar