如何测试Express构建的REST API中的控制器?
这种测试慢到影响开发效率的情况太常见了——我当初用Express写API的时候也踩过这个坑,全量数据库操作加迁移确实能把测试时间拖到让人崩溃。下面给你几个实用的优化方向,亲测有效:
1. 改用内存数据库替代真实数据库
这是提升速度最显著的办法之一。内存数据库完全在内存中运行,没有磁盘IO开销,创建、销毁、读写都快得飞起。
如果你用MongoDB:用
mongodb-memory-server包,它可以在测试时启动一个临时的内存MongoDB实例,测试结束自动销毁,不需要手动清库或迁移。示例代码:// 测试全局初始化文件 const { MongoMemoryServer } = require('mongodb-memory-server'); const mongoose = require('mongoose'); let mongoServer; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); await mongoose.connect(mongoServer.getUri()); // 这里可以一次性填充基础测试数据,不用每个测试都跑 }); afterAll(async () => { await mongoose.disconnect(); await mongoServer.stop(); });如果你用SQL类数据库(PostgreSQL/MySQL):用SQLite的内存模式,或者
pg-mem(PostgreSQL内存模拟),同样能避免磁盘操作。
2. 复用数据库连接与迁移,只清理数据而非重建库
不要每个测试文件都重新启动数据库、跑迁移脚本——迁移脚本本身就很耗时。可以在全局测试初始化阶段完成一次数据库连接和迁移,之后每个测试只需要清理数据(比如批量删除或 truncate 表),而不是重新建库迁移。
比如用Jest的globalSetup和globalTeardown,或者Mocha的根级before/after钩子:
// 根级before钩子,所有测试只执行一次 before(async () => { await db.connect(); // 连接数据库 await db.runMigrations(); // 执行一次迁移 }); // 每个测试前清理数据 beforeEach(async () => { await db.truncateAllTables(); // 快速清空所有表数据 await db.seedTestData(); // 填充必要的测试基准数据 }); after(async () => { await db.disconnect(); });
3. 用事务隔离测试(数据库支持的话)
如果你的数据库支持事务(比如PostgreSQL、MySQL InnoDB),这是更高效的隔离方式:每个测试开始时开启一个事务,测试结束后回滚事务,这样所有测试操作都不会真正写入数据库,数据自动回到初始状态,比清库快N倍。
示例(用Knex.js):
let trx; beforeEach(async () => { trx = await db.transaction(); // 开启事务 // 替换全局数据库实例为事务实例,确保所有操作都在事务内 app.set('db', trx); }); afterEach(async () => { await trx.rollback(); // 回滚事务,数据恢复 });
4. 合理Mock模型层(谨慎使用)
如果你的模型层已经单独做了单元测试,控制器测试可以Mock模型的数据库操作,跳过真实数据库交互,专注测试业务逻辑和响应构建。这种方式速度极快,但要注意不能完全替代真实数据库测试——核心业务路径还是要跑真实集成测试。
示例(用Jest Mock):
// Mock User模型 jest.mock('../models/User'); test('POST /users 应该返回创建的用户', async () => { // 模拟模型的create方法返回值 User.create.mockResolvedValue({ id: 1, name: '测试用户', email: 'test@example.com' }); const response = await request(app) .post('/users') .send({ name: '测试用户', email: 'test@example.com' }); expect(response.statusCode).toBe(201); expect(response.body).toEqual({ id: 1, name: '测试用户', email: 'test@example.com' }); // 验证模型方法被正确调用 expect(User.create).toHaveBeenCalledWith({ name: '测试用户', email: 'test@example.com' }); });
5. 测试分层与并行执行
把测试分成三类,按需执行:
- 单元测试:Mock模型层,只测控制器逻辑,最快,本地开发时经常跑
- 集成测试:用内存数据库+事务,测控制器与数据库的真实交互,速度较快,提交代码前跑
- 端到端测试:用真实测试数据库,全流程验证,最慢,只在CI或发布前跑
另外,开启测试框架的并行执行功能(比如Jest默认支持并行),把测试文件拆分,让多个测试同时运行,大幅减少总耗时。
内容的提问来源于stack exchange,提问作者Magix

