You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 19:18:02