当前位置: 首页 > news >正文

【Nest】集成测试

什么是集成测试(Integration Test)

  • 定义:集成测试在单元测试之上,验证模块之间真实交互是否正确(例如 service 与 repository、controller 与 service 的真实组合),不把内部重要依赖 mock 掉(或只 mock 外部第三方服务),但通常仍避免对生产外部系统(真实第三方 API、生产 DB)进行依赖。
  • 目的:检验不同层/模块的组合行为(比如 ORM 查询能否按预期返回、controller 路由到 service 并把 DB 数据正确返回等),比单元测试更能发现集成面的错误,但比 e2e 更局部、更快。

常见场景

  1. Service + Repository 的集成测试:真实使用测试数据库(如 sqlite in-memory / test container DB),验证 ORM 查询、实体映射、事务行为。
  2. Controller HTTP 层集成测试(通常也称为部分 e2e):启动 INestApplication(或 FastifyAdapter 等),通过 supertest 发 HTTP 请求,验证路由、管道、守卫和 service 的集成,但仍可用测试 DB、或 mock 外部服务。

集成测试的两种常见实现方式(对比)

  • 使用真实测试 DB(推荐):例如 sqlite 内存或单独的测试 Postgres(Docker / Testcontainers)。优点:接近真实;缺点:需要注意隔离、启动速度。
  • 用部分 mock(例如 mock 外部 API、但真实 DB):保持 DB 真实、网络/邮件/第三方被 mock,常见于业务有外部依赖时。

示例项目说明

假设有 User 实体、UsersServiceUsersController,使用 TypeORM。我们做两个集成测试:

  1. users.service.int-spec.ts —— Service + Repository(TypeORM sqlite 内存)
  2. users.e2e-spec.ts —— 启动 Nest 应用并用 supertest 调用 HTTP 接口(使用同样的测试 DB)

代码示例

先给出最小实现用于测试:

// src/users/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';@Entity('users')
export class User {@PrimaryGeneratedColumn()id: number;@Column()name: string;
}
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { InjectRepository } from '@nestjs/typeorm';@Injectable()
export class UsersService {constructor(@InjectRepository(User)private readonly repo: Repository<User>,) {}create(name: string) {const u = this.repo.create({ name });return this.repo.save(u);}findOne(id: number) {return this.repo.findOneBy({ id });}findAll() {return this.repo.find();}
}
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';@Controller('users')
export class UsersController {constructor(private readonly svc: UsersService) {}@Post()create(@Body('name') name: string) {return this.svc.create(name);}@Get()list() {return this.svc.findAll();}@Get(':id')get(@Param('id') id: string) {return this.svc.findOne(Number(id));}
}
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';@Module({imports: [TypeOrmModule.forFeature([User])],providers: [UsersService],controllers: [UsersController],
})
export class UsersModule {}

集成测试 A:Service + Repository(TypeORM + sqlite :memory:)

重点:在测试 TestingModule导入 TypeOrmModule.forRoot 指向 sqlite 内存数据库,并导入 UsersModule。设 synchronize: truedropSchema: true(测试专用)以保证干净的 schema。

// test/users.service.int-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UsersService } from '../src/users/users.service';
import { User } from '../src/users/user.entity';describe('UsersService (integration)', () => {let moduleRef: TestingModule;let service: UsersService;let repo: Repository<User>;beforeAll(async () => {moduleRef = await Test.createTestingModule({imports: [// 连接到 sqlite in-memory 数据库,仅供测试使用TypeOrmModule.forRoot({type: 'sqlite',database: ':memory:',dropSchema: true,      // 测试时重建 schemaentities: [User],synchronize: true,     // 测试时自动同步实体(仅测试)logging: false,}),TypeOrmModule.forFeature([User]),],providers: [UsersService],}).compile();service = moduleRef.get<UsersService>(UsersService);repo = moduleRef.get<Repository<User>>(getRepositoryToken(User));});afterAll(async () => {// 关闭连接await moduleRef.close();});beforeEach(async () => {// 测试隔离:清空表(或使用事务回滚策略,见后文)await repo.clear();});it('should create and find user', async () => {const created = await service.create('alice');expect(created).toHaveProperty('id');expect(created.name).toBe('alice');const found = await service.findOne(created.id);expect(found.name).toBe('alice');});it('findAll returns multiple users', async () => {await service.create('u1');await service.create('u2');const all = await service.findAll();expect(all.length).toBe(2);const names = all.map(u => u.name).sort();expect(names).toEqual(['u1', 'u2']);});
});

要点解释

  • 使用 sqlite :memory: 不会写磁盘,速度快。
  • dropSchema: true + synchronize: true 可在测试每次启动时保证 schema 干净(注意:不要在生产使用)。
  • repo.clear() 在每个 beforeEach 中清数据保证测试隔离(也可以用事务回滚替代)。

集成测试 B:Controller HTTP 层集成测试(用 supertest)

这类测试会启动 INestApplication,并使用 supertest 发起 HTTP 请求。仍然用 sqlite 内存 DB。也可以在测试时 overrideProvider 来 mock发送邮件、第三方 API 等。

// test/users.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from '../src/users/users.module';
import { User } from '../src/users/user.entity';describe('UsersController (e2e/integration)', () => {let app: INestApplication;beforeAll(async () => {const moduleFixture: TestingModule = await Test.createTestingModule({imports: [TypeOrmModule.forRoot({type: 'sqlite',database: ':memory:',dropSchema: true,entities: [User],synchronize: true,}),UsersModule,],}).compile();app = moduleFixture.createNestApplication();// 如果你在 controller 使用 ValidationPipe,记得在测试中也加上app.useGlobalPipes(new ValidationPipe({ whitelist: true }));await app.init();});afterAll(async () => {await app.close();});it('/users (POST) -> create and GET /users', async () => {const server = app.getHttpServer();// 创建用户await request(server).post('/users').send({ name: 'bob' }).expect(201).then(res => {expect(res.body).toHaveProperty('id');expect(res.body.name).toBe('bob');});// 列表const list = await request(server).get('/users').expect(200);expect(Array.isArray(list.body)).toBe(true);expect(list.body.length).toBe(1);expect(list.body[0].name).toBe('bob');});it('/users/:id (GET) -> 404 for missing', async () => {const server = app.getHttpServer();await request(server).get('/users/9999').expect(200); // 注意:Our service returns null; 若要返回 404 可在 controller 里处理});
});

注意:上面示例中 GET /users/9999 返回的状态取决于 controller/service 是否将 null 转化为 404;真实项目中你可能在 service 抛出 NotFoundException 或在 controller 中判断并抛 404。调整断言以匹配你的实现。

http://www.dtcms.com/a/507851.html

相关文章:

  • ELK运维之路(Logstash基础使用-7.17.24)
  • 快速排序(JAVA详细讲解快速排序的四种方式)
  • 数据结构四大简单排序算法详解:直接插入排序、选择排序、基数排序和冒泡排序
  • 官渡网站建设wordpress单页面制作
  • 企业电子商务网站开发数据库设计昆明seo博客
  • 东道 网站建设erp系统哪家做得好
  • 现代 Web 开发中检测用户离开页面的完整方案(附 Vue 实现)
  • [crackme]029-figugegl.1
  • 网站建站分辨率腾讯企点怎么注册
  • 第四章:L2CAP 的“数据语言”——揭秘蓝牙通信的报文格式
  • 【代码随想录算法训练营——Day43(Day42周日休息)】动态规划——300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组
  • block的样式有哪些?如果copy的话分别会有啥样式
  • 如何做网络投票网站大数据开发工程师
  • 提示词 prompt 快速上手
  • 网站降权查询工具lnmp中安装wordpress
  • 一个空间放两个网站蓟门桥网站建设
  • DPC和DPC-KNN算法
  • git中tag标签远程管理
  • Babylon.js UtilityLayerRenderer 深度解析:创建3D工具与调试层的完整指南
  • 如何制造一个网站网站的图片怎么更换
  • 区块链安全评估:守护数字世界的“安全密码”
  • 多语言网站建设公司教你做企业网站
  • 第19节-非规范化数据类型-Drop-Type
  • docker desktop的容器间通信
  • 宝安做网站的公司企业文化经典句子
  • 学校二级网站建设百度关键词优化怎么做
  • 百度前端面试准备
  • 立创EDA学习(一、新建项目与自定义元件)
  • dify项目智能记账
  • 使用Jmeter进行接口测试:HTTP请求与响应报文结构详解