自动化测试是软件开发的基础,至少测试要覆盖到系统里的一些比较敏感的部分。我们需要使用不同类型的测试,比如集成测试、单元测试、e2e 测试等等,Nest 提供了一些工具可以改善创建这些测试的体验,默认集成了 Jest 测试框架,你也可以使用自己熟悉的测试框架。
安装
先得安装需要的包:
npm install @nestjs/testing --save-dev
单元测试
Unit testing,单元测试。Jest 是一个非常成熟的测试框架,框架可以用来运行测试,里面也提供了 assert 函数,测试替身,可以用来 mocking 或 spying 等等。
假设应用里有两个类:CatsController 与 CatsService。下面我们手工强制让调用 catsService.findAll() 方法的时候返回 result,这样我们就可以测试 catsController.findAll() 方法是否可以返回我们期望的结果了。
import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; describe('CatsController', () => { let catsController: CatsController; let catsService: CatsService; beforeEach(() => { catsService = new CatsService(); catsController = new CatsController(catsService); }); describe('findAll', () => { it('should return an array of cats', async () => { const result = ['test']; jest.spyOn(catsService, 'findAll').mockImplementation(() => result); expect(await catsController.findAll()).toBe(result); }) }); });
上面这个测试并没有用到 Nest 测试工具,测试的类是手动实例化的,这种测试叫 isolated tests(独立测试)。
测试工具
在 @nestjs/testing 里提供的工具,重新创建上面这个测试。
import { Test } from '@nestjs/testing'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; describe('CatsController', () => { let catsController: CatsController; let catsService: CatsService; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [CatsController], providers: [CatsService], }).compile(); catsService = module.get(CatsService); catsController = module.get(CatsController); }); describle('findAll', () => { it('should return an array of cats', async () => { const result = ['test']; jest.spyOn(catsService, 'findAll').mockImplementation(() => result); expect(await catsController.findAll()).toBe(result); }) }); });
Test 类里有个 createTestingModule() 方法,接收的对象参数跟 @Module 一样。这个方法创建会一个 TestingModule 实例,里面有几个方法,在做单元测试时只有一个方法有用,就是 compile() ,模块被编译之后,可以使用 get() 方法获取到任意实例。要 mock 真正的实例,可以使用自定义 provider 覆盖已有 provider。
End-to-end 测试
我们的应用会越来越复杂,手工测试应用里提供的所有接口也会越来越困难,可以用 e2e 测试帮我们去测试。
import * as request from 'supertest'; import { Test } from '@nestjs/testing'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; import { INestApplication } from '@nestjs/common'; describle('Cats', () => { let app: INestApplication; let catsService = { findAll: () => ['test'] }; beforeAll(async () => { const module = await Test.createTestingModule({ imports: [CatsModule], }) .overrideProvider(CatsService) .useValue(catsService) .compile(); app = module.createTestingModule(); await app.init(); }); it('/GET cats', () => { return request(app.getHttpServer()) .get('/cats') .expect(200) .expect({ data: catsService.findAll(), }); }); afterAll(async () => { await app.close(); }); });
e2e 测试可以放在 e2e 目录里面,测试文件要有 .e2e-spec 或 .e2e-test 后缀。
cats.e2e-spec.ts 文件包含一个 HTTP 接口测试(/cats)。我们要用 app.getHttpServer() 方法获取到在后台运行的 Nest 应用的 HTTP 服务器。
TestingModule 实例提供了 overrideProvider() 方法,可以覆盖已有 provider ,还可以使用 overrideGuard(),overrideInterceptor(),overrideFilter() 还有 overridePipe 覆盖相应的东西。
编译的模块有下面几个方法:
- createNestApplication(),基于模块创建 Nest 实例(返回 INestApplication)。需要使用 init() 方法手工初始化应用。
- createNestMicroservice(),基于模块创建 Nest 微服务实例(返回 INestMicroservice)。
- get(),获取控制器、provider 实例。
- select(),从选择的模块里提取特定的实例。