How to Build a Robust Node.js Testing Workflow with Jest, SuperTest, and MongoDB Memory Server
This article walks through setting up a comprehensive testing workflow for a Node.js backend, covering Jest configuration, unit and integration tests with SuperTest, in‑memory MongoDB, login helpers, mocking, snapshot testing, coverage reporting, CI integration, and the adoption of TDD/BDD practices.
1. Shared Content and Tech Stack
The article shares practical experience of building a test workflow for a Node.js service, aiming to provide a developer‑friendly experience, efficient unit and integration tests, module isolation, SuperTest integration, in‑memory database services, and insights into mocks, snapshots, TDD and BDD.
Use Jest to create a friendly testing workflow.
Write efficient unit and integration test cases.
Encapsulate modules to simplify test code.
Combine application and test processes with SuperTest.
Create efficient in‑memory database services for isolated test suites.
Understand mock, snapshot, and test coverage features.
Grasp TDD and BDD concepts.
2. About Linglong
Linglong is JD's intelligent design platform offering online design services such as image design, video design, page design, and practical tools like batch cutout, resizing, color matching, and watermarking.
3. Architecture and Test Framework Selection
The project follows a front‑back separation architecture. The front end uses a React family stack with Next.js for server‑side rendering. The back end is served behind an Nginx cluster, which forwards requests to Linglong application servers that interact with external services, caches, and databases.
Main Test Framework Comparison
Comparing popular Node.js test frameworks shows Mocha’s early adoption, while Jest and AVA are newer with rising popularity. Built‑in features often integrate better with the framework, and Jest’s mocking capabilities are especially useful.
Conclusion: Jest is chosen as the base test framework.
4. From 0 to 1 Implementation
1. Jest Configuration
Install Jest and ts‑jest for TypeScript support: yarn add --dev jest ts-jest @types/jest Create jest.config.js with essential settings:
module.exports = {
globals: {
'ts-jest': { tsConfig: 'tsconfig.test.json' }
},
testEnvironment: 'node',
roots: ['<rootDir>/src/', '<rootDir>/test/'],
testMatch: ['<rootDir>/test/**/*.ts'],
testPathIgnorePatterns: ['<rootDir>/test/@.+/'],
moduleNameMapper: { '^~/(.*)': '<rootDir>/src/$1' }
};Define scripts in package.json for running tests:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
}
}2. First Unit Test Case
Example test for the homepage carousel API:
import { forEach, isArray } from 'lodash';
import { JFSRegex, URLRegex } from '~/utils/regex';
import request from 'request-promise';
const baseUrl = 'http://ling-dev.jd.com/server/api';
test('carousel should return 5 items with correct data', async () => {
const res = await request.get(baseUrl + '/carousel/pictures');
expect(res.statusCode).toBe(200);
expect(isArray(res.body)).toBe(true);
expect(res.body.length).toBe(5);
forEach(res.body, picture => {
expect(picture).toMatchObject({
url: expect.stringMatching(JFSRegex),
href: expect.stringMatching(URLRegex)
});
});
});Run the test with npx jest or yarn test.
3. SuperTest Integration
Wrap the Koa app with SuperTest to avoid starting a separate server:
import http from 'http';
import supertest from 'supertest';
import app from '~/app';
export const request = supertest(http.createServer(app.callback()));Use the request object directly in tests, e.g., request.get('/api/carousel/pictures').
4. In‑Memory Database Service
Use mongodb-memory-server to spin up isolated MongoDB instances for tests. Configure a custom Jest environment that starts a memory server, injects its URI into global.testConfig.mongo.uri, and connects Mongoose accordingly.
Disable automatic binary download in package.json configuration:
"config": {
"mongodbMemoryServer": {
"version": "4.0",
"disablePostinstall": "1",
"md5Check": "1"
}
}5. Login Helper
Create a reusable login helper that generates a user (with random username/nickname if missing), logs in via a test‑only endpoint, and returns a SuperTest agent with persisted cookies:
export async function login(userData) {
userData = cloneDeep(userData);
if (!userData.username) userData.username = chance.word({ length: 8 });
if (!userData.nickname) userData.nickname = chance.word({ length: 8 });
const request = makeAgent();
const res = await request.post('/api/login-test').send(userData);
assign(request, pick(res.body, ['user', 'otherValidKey']));
return request;
}6. Mocking
Place a mock implementation in utils/__mocks__/detect.ts that always returns { ok: true, sensitive: false }. Activate it globally via jest.mock('~/utils/detect.ts') in a setup file, and unmock in the specific test suite that needs the real implementation.
7. Snapshot Testing
Use expect(res.body).toMatchSnapshot() to capture large JSON responses (e.g., template parsing) and compare them in subsequent runs. Update snapshots with jest --updateSnapshot or the u key in watch mode.
8. Integration Tests
Demonstrates a full flow: login as a designer, create three folders, rename the second, verify details, list folders, delete the last one, and assert the final count. The tests reuse the login helper and SuperTest agent to maintain session state.
5. Test Coverage
Run jest --coverage to generate an Istanbul‑based report at coverage/lcov-report/index.html. The report shows line, branch, function, and statement coverage for the entire codebase.
6. Continuous Integration
Configure a CI pipeline (e.g., GitLab CI) to run linting and tests on each push. Example .gitlab-ci.yml includes a test job that caches node_modules, installs dependencies, runs yarn lint, and executes yarn test. A subsequent deploy-stage job can be triggered after successful tests.
7. Introducing TDD and BDD
TDD (Test‑Driven Development) emphasizes writing failing tests before code, while BDD (Behavior‑Driven Development) focuses on describing behavior from a user’s perspective. The article recommends a pragmatic blend: write tests when they help design, otherwise develop iteratively, but always ensure tests pass before merging.
8. Conclusion
Automated testing provides a reliable mechanism for regression testing, improving system stability. While writing and maintaining tests incurs cost, the benefits of early bug detection, consistent quality, and faster iteration outweigh the investment.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD.com Experience Design Center
Professional, creative, passionate about design. The JD.com User Experience Design Department is committed to creating better e-commerce shopping experiences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
