Understanding Integration Testing: A Comprehensive Guide


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/functions | Run When | Purpose e.g |
beforeAll() | One before all test | Db connect/server start, etc |
afterAll() | Once after all test | Db disconnect/stop server,etc |
beforeEach() | Before every individual test case | Reset data, mock clear, etc. |
afterEach() | After every individual test case | Useful 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 / API | Description |
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.
Subscribe to my newsletter
Read articles from shivam kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
