使用NestJS框架时如何不做Mock、对接真实数据库编写单元测试用例
你需要的这种不mock依赖、直接对接真实数据库的测试,严格来说属于集成测试范畴,在NestJS中可以按照以下步骤实现:
前置准备
提前创建专属的测试环境数据库,绝对不要直接使用开发、生产环境的数据库执行测试,避免测试操作污染业务数据。
实现步骤
1. 配置测试专属环境变量
在项目根目录新建.env.test文件,写入测试库的连接配置,示例:
DB_HOST=localhost DB_PORT=3306 DB_USER=root DB_PASSWORD=123456 DB_NAME=nest_test # 专属测试库名
在package.json中修改测试启动命令,指定加载测试环境变量:
"scripts": { "test": "dotenv -e .env.test jest", "test:e2e": "dotenv -e .env.test jest --config ./test/jest-e2e.json" }
如果是Windows系统可以搭配cross-env做跨系统兼容。
2. Service测试示例
不需要mock任何provider,直接初始化完整的业务模块对接真实数据库,用事务回滚实现单测用例之间的隔离:
import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; import { UserService } from './user.service'; import { User } from './entities/user.entity'; import { DataSource } from 'typeorm'; describe('UserService (real DB)', () => { let service: UserService; let dataSource: DataSource; let queryRunner; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRoot({ type: 'mysql', // 替换为你实际使用的数据库类型 host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: [User], synchronize: false, // 关闭自动同步,用迁移保证表结构正确 }), TypeOrmModule.forFeature([User]), ], providers: [UserService], }).compile(); service = module.get<UserService>(UserService); dataSource = module.get<DataSource>(DataSource); // 执行迁移初始化测试库表结构 await dataSource.runMigrations(); }); beforeEach(async () => { // 每个测试用例开启独立事务 queryRunner = dataSource.createQueryRunner(); await queryRunner.startTransaction(); }); afterEach(async () => { // 回滚事务,清理本次测试产生的所有数据 await queryRunner.rollbackTransaction(); await queryRunner.release(); }); afterAll(async () => { // 所有测试完成后关闭数据库连接 await dataSource.destroy(); }); it('should create user correctly', async () => { const user = await service.create({ username: 'testuser', email: 'test@example.com', }); expect(user.id).toBeDefined(); expect(user.username).toBe('testuser'); // 直接查库验证数据写入正确性 const savedUser = await queryRunner.manager.findOneBy(User, { id: user.id }); expect(savedUser.email).toBe('test@example.com'); }); });
3. Controller测试示例
和Service测试逻辑一致,额外初始化Nest应用实例,用supertest发送HTTP请求验证接口逻辑:
import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserModule } from './user.module'; import { DataSource } from 'typeorm'; describe('UserController (real DB)', () => { let app: INestApplication; let dataSource: DataSource; let queryRunner; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRoot({ // 和Service测试的数据库配置保持一致 type: 'mysql', host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, }), UserModule, ], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); dataSource = moduleFixture.get<DataSource>(DataSource); await dataSource.runMigrations(); }); beforeEach(async () => { queryRunner = dataSource.createQueryRunner(); await queryRunner.startTransaction(); }); afterEach(async () => { await queryRunner.rollbackTransaction(); await queryRunner.release(); }); afterAll(async () => { await dataSource.destroy(); await app.close(); }); it('POST /user should return created user', async () => { const res = await request(app.getHttpServer()) .post('/user') .send({ username: 'testuser', email: 'test@example.com' }) .expect(201); expect(res.body.id).toBeDefined(); expect(res.body.username).toBe('testuser'); }); });
注意事项
- 事务回滚的隔离方案不适用于涉及分布式事务、多数据源的测试场景,这种情况可以改用
afterEach中执行TRUNCATE语句清空所有测试用到的表 - 如果觉得测试执行速度慢,可以把测试库替换为SQLite内存数据库,只需要修改TypeORM配置中的type为
sqlite,database为:memory:即可,执行速度会提升数倍,注意适配两种数据库的语法差异 - 如果需要做自动化流水线测试,可以在流水线启动阶段临时拉起一个数据库容器执行测试,测试结束后直接销毁容器即可
内容的提问来源于stack exchange,提问作者GAURAV RAJPUT
相关产品推荐
相关产品推荐

