nodejs进行单元测试、集成测试和端到端(E2E)测试
h5打开以查看
1. 单元测试
目标:测试单个函数、类或模块的独立功能,隔离所有外部依赖(如数据库、API、文件系统)。
哲学:验证代码的“单元”在给定特定输入时,是否产生预期的输出。
常用工具:
-
测试运行器/框架:Jest (最流行,开箱即用)、Mocha (需更多配置)、Vitest (新兴,快速)。
-
断言库:Jest (内置)、Chai (常与 Mocha 配对)。
-
** mocking/Stubbing**:Jest (内置)、Sinon.js (常与 Mocha 配对)。
示例 (使用 Jest)
假设有一个简单的函数 math.js:
javascript
// math.js
function add(a, b) {
return a + b;
}
function fetchData(callback) {
setTimeout(() => callback('some data'), 100);
}
module.exports = { add, fetchData };
对应的测试文件 math.test.js:
javascript
// math.test.js
const { add, fetchData } = require('./math');
// 同步函数测试
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
// 异步回调函数测试 (使用 Jest 的 done)
test('fetchData returns \'some data\'', (done) => {
function callback(data) {
try {
expect(data).toBe('some data');
done(); // 告诉 Jest 测试完成
} catch (error) {
done(error);
}
}
fetchData(callback);
});
// 使用 Mock 函数测试函数是否被调用
test('fetchData calls the callback', () => {
const mockCallback = jest.fn(); // 创建一个 Mock 函数
fetchData(mockCallback);
// 因为 fetchData 是异步的,我们需要断言 mock 函数最终被调用了
setTimeout(() => {
expect(mockCallback).toHaveBeenCalledWith('some data');
}, 150);
});
运行测试:npx jest math.test.js
2. 集成测试
目标:测试多个模块、组件或服务如何协同工作。会使用真实的依赖或近似真实的测试替身(如内存数据库)。
哲学:验证各个“单元”连接在一起时是否能按预期通信和协作。
常用工具:同样的测试框架 (Jest, Mocha),但配置不同。
示例:测试 Service 与 Repository
假设我们有一个 User 系统:
javascript
// userRepository.js (操作数据库)
class UserRepository {
async findByEmail(email) { /* ... 数据库查询 ... */ }
}
// userService.js (业务逻辑)
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async registerUser(userData) {
const existingUser = await this.userRepository.findByEmail(userData.email);
if (existingUser) {
throw new Error('User already exists');
}
// ... 创建用户逻辑 ...
return newUser;
}
}
对应的集成测试 userService.integration.test.js:
javascript
// 这里我们使用一个真实的内存数据库(如 SQLite)进行测试,而不是完全 Mock
const UserService = require('./userService');
const UserRepository = require('./userRepository');
const db = require('./my-database-connection'); // 真实的数据库连接,但在测试中指向测试库
describe('UserService Integration Test', () => {
let userRepository;
let userService;
beforeAll(async () => {
// 初始化数据库,可能是运行迁移脚本
await db.migrate.latest();
});
beforeEach(() => {
userRepository = new UserRepository(db);
userService = new UserService(userRepository);
});
afterEach(async () => {
// 清空测试数据,保证每个测试用例的独立性
await db('users').truncate();
});
afterAll(async () => {
// 关闭数据库连接
await db.destroy();
});
test('should successfully register a new user', async () => {
const userData = { email: 'test@example.com', name: 'Test User' };
const result = await userService.registerUser(userData);
expect(result).toHaveProperty('id');
expect(result.email).toBe(userData.email);
// 进一步验证数据是否真的写入了数据库
const userInDb = await userRepository.findByEmail(userData.email);
expect(userInDb).not.toBeNull();
});
test('should throw error if email already exists', async () => {
// 先插入一条数据
await userRepository.create({ email: 'exist@example.com', name: 'Existing' });
await expect(
userService.registerUser({ email: 'exist@example.com', name: 'Test' })
).rejects.toThrow('User already exists');
});
});
3. 端到端 (E2E) 测试
目标:从用户的角度测试整个应用程序的完整工作流。模拟真实用户的操作,包括启动完整的服务器、数据库、前端等。
哲学:验证整个系统是否满足外部要求和用户目标。
常用工具:
-
对 HTTP API:Supertest (非常流行,用于测试 Express/Koa 等 HTTP 服务器)。
-
对带UI的Web应用:Playwright (推荐,强大且现代)、Cypress、Puppeteer、Selenium。
示例:使用 Supertest 测试 API
假设有一个 Express 应用 app.js:
javascript
// app.js
const express = require('express');
const app = express();
app.use(express.json());
const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);
module.exports = app; // 关键:导出 app 而不监听端口
E2E 测试文件 api.e2e.test.js:
javascript
const request = require('supertest');
const app = require('../app'); // 导入我们的 Express app 实例
const db = require('./db');
// 在测试前建立数据库连接,之后清理
beforeAll(async () => await db.connect());
afterEach(async () => await db.clear());
afterAll(async () => await db.close());
describe('User API E2E Tests', () => {
it('POST /api/users -> should create a new user', async () => {
const newUser = { name: 'Alice', email: 'alice@example.com' };
const response = await request(app) // 将 app 传给 supertest
.post('/api/users')
.send(newUser)
.expect('Content-Type', /json/)
.expect(201); // 断言 HTTP 状态码
// 断言响应体
expect(response.body).toHaveProperty('_id');
expect(response.body.name).toBe(newUser.name);
expect(response.body.email).toBe(newUser.email);
});
it('GET /api/users -> should return list of users', async () => {
// 先预置一些数据
// await UserModel.create({...})
const response = await request(app)
.get('/api/users')
.expect(200);
expect(Array.isArray(response.body)).toBeTruthy();
// ...更多断言
});
});
运行测试:npx jest api.e2e.test.js
总结与建议
| 测试类型 | 测试什么? | 工具链示例 | 速度 | 范围 |
|---|---|---|---|---|
| 单元测试 | 独立的函数、类 | Jest (首选) | 非常快 | 窄,孤立 |
| 集成测试 | 模块间的协作 | Jest/Mocha + 真实依赖 (如DB) | 中等 | 中等 |
| 端到端测试 | 整个应用工作流 | Supertest (API) / Playwright (UI) | 慢 | 宽,完整 |
现代工作流建议:
-
首选 Jest:它对大多数项目来说都是最佳选择,因为它集成了测试运行器、断言、Mock 和代码覆盖率,无需配置。
-
测试金字塔:编写大量的单元测试,一定数量的集成测试,和少量关键的E2E测试。这保证了测试套件的速度和有效性。
-
在 CI/CD 中运行:将测试套件集成到你的 GitHub Actions、GitLab CI 等持续集成流程中,确保每次提交都通过所有测试。
-
使用
__tests__目录:将测试文件放在源码旁边(如src/__tests__/math.test.js)或统一放在项目根目录的test文件夹中。 -
Mock 外部服务:在单元和集成测试中,使用 Jest 的
jest.mock()或 Nock 等库来 Mock 第三方 API 调用,使测试更快、更稳定。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)