乐闻世界logo
搜索文章和话题

NestJS 测试和最佳实践有哪些?

2月17日 22:36

NestJS 测试概述

NestJS 提供了完整的测试支持,包括单元测试、集成测试和端到端测试。测试框架基于 Jest,提供了丰富的测试工具和实用程序。

测试类型

1. 单元测试(Unit Testing)

单元测试用于测试单个函数、类或模块的行为,通常不涉及外部依赖。

服务单元测试示例

typescript
import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { Repository } from 'typeorm'; describe('UsersService', () => { let service: UsersService; let repository: Repository<User>; const mockUserRepository = { create: jest.fn(), save: jest.fn(), find: jest.fn(), findOne: jest.fn(), update: jest.fn(), delete: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getRepositoryToken(User), useValue: mockUserRepository, }, ], }).compile(); service = module.get<UsersService>(UsersService); repository = module.get<Repository<User>>(getRepositoryToken(User)); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('create', () => { it('should create a new user', async () => { const createUserDto = { name: 'John', email: 'john@example.com' }; const user = { id: 1, ...createUserDto }; mockUserRepository.create.mockReturnValue(user); mockUserRepository.save.mockResolvedValue(user); const result = await service.create(createUserDto); expect(repository.create).toHaveBeenCalledWith(createUserDto); expect(repository.save).toHaveBeenCalledWith(user); expect(result).toEqual(user); }); }); describe('findAll', () => { it('should return an array of users', async () => { const users = [ { id: 1, name: 'John', email: 'john@example.com' }, { id: 2, name: 'Jane', email: 'jane@example.com' }, ]; mockUserRepository.find.mockResolvedValue(users); const result = await service.findAll(); expect(repository.find).toHaveBeenCalled(); expect(result).toEqual(users); }); }); });

2. 集成测试(Integration Testing)

集成测试用于测试多个组件之间的交互,通常涉及数据库、外部服务等。

控制器集成测试示例

typescript
import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/users (GET)', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect([]); }); it('/users (POST)', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John', email: 'john@example.com' }) .expect(201) .expect(res => { expect(res.body).toHaveProperty('id'); expect(res.body.name).toBe('John'); }); }); afterEach(async () => { await app.close(); }); });

3. 端到端测试(E2E Testing)

端到端测试模拟真实用户场景,测试整个应用程序的流程。

typescript
import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('UsersController (e2e)', () => { let app: INestApplication; let userId: number; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); }); it('should create a user', async () => { const response = await request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com' }) .expect(201); userId = response.body.id; expect(response.body).toHaveProperty('id'); expect(response.body.name).toBe('John Doe'); }); it('should get all users', async () => { const response = await request(app.getHttpServer()) .get('/users') .expect(200); expect(Array.isArray(response.body)).toBe(true); }); it('should get a user by id', async () => { const response = await request(app.getHttpServer()) .get(`/users/${userId}`) .expect(200); expect(response.body.id).toBe(userId); }); it('should update a user', async () => { const response = await request(app.getHttpServer()) .patch(`/users/${userId}`) .send({ name: 'Jane Doe' }) .expect(200); expect(response.body.name).toBe('Jane Doe'); }); it('should delete a user', async () => { await request(app.getHttpServer()) .delete(`/users/${userId}`) .expect(200); }); afterAll(async () => { await app.close(); }); });

测试工具和实用程序

1. Test.createTestingModule()

创建测试模块,用于配置测试环境。

typescript
const module: TestingModule = await Test.createTestingModule({ imports: [AppModule], providers: [UsersService], }).compile();

2. Mock 提供者

使用 mock 对象替换真实的提供者。

typescript
{ provide: UsersService, useValue: { findAll: jest.fn().mockResolvedValue([]), findOne: jest.fn().mockResolvedValue({ id: 1 }), }, }

3. Jest 函数

使用 Jest 的 mock 功能。

typescript
jest.fn() // 创建 mock 函数 jest.mock() // 模块 mock jest.spyOn() // 监听对象方法 mockResolvedValue() // 设置返回值 mockRejectedValue() // 设置拒绝值 mockReturnValue() // 设置同步返回值 mockReturnValueOnce() // 设置一次性返回值

最佳实践

1. 测试组织

shell
src/ ├── users/ │ ├── users.controller.spec.ts │ ├── users.service.spec.ts │ └── users.module.spec.ts test/ ├── app.e2e-spec.ts └── users.e2e-spec.ts

2. 测试命名约定

  • 使用 describe 分组相关测试
  • 使用 ittest 描述单个测试用例
  • 测试名称应该清晰描述测试内容
typescript
describe('UsersService', () => { describe('create', () => { it('should create a new user', async () => { // 测试逻辑 }); it('should throw an error if email already exists', async () => { // 测试逻辑 }); }); });

3. 测试隔离

每个测试应该独立运行,不依赖其他测试的状态。

typescript
beforeEach(() => { // 在每个测试前重置状态 }); afterEach(() => { // 在每个测试后清理 });

4. 测试覆盖率

使用 Jest 的覆盖率功能确保代码质量。

bash
npm run test:cov

package.json 中配置:

json
{ "jest": { "collectCoverageFrom": [ "src/**/*.(t|j)s", "!src/main.ts", "!src/**/*.module.ts", "!src/**/*.dto.ts" ], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } } }

5. Mock 外部依赖

对于数据库、API 等外部依赖,使用 mock 避免实际调用。

typescript
const mockRepository = { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), delete: jest.fn(), };

6. 测试异步代码

正确处理异步代码的测试。

typescript
// 使用 async/await it('should return user', async () => { const result = await service.findOne(1); expect(result).toBeDefined(); }); // 使用 Promise it('should return user', () => { return service.findOne(1).then(result => { expect(result).toBeDefined(); }); }); // 使用 done 回调 it('should return user', (done) => { service.findOne(1).then(result => { expect(result).toBeDefined(); done(); }); });

7. 测试边界情况

确保测试覆盖各种边界情况和错误场景。

typescript
describe('findOne', () => { it('should return user when found', async () => { // 正常情况 }); it('should throw NotFoundException when user not found', async () => { // 用户不存在的情况 }); it('should throw BadRequestException when id is invalid', async () => { // 无效 ID 的情况 }); });

8. 使用测试数据库

在集成测试中使用测试数据库,避免影响生产数据。

typescript
beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'sqlite', database: ':memory:', entities: [User], synchronize: true, }), ], }).compile(); });

性能测试

1. 负载测试

使用工具如 Artillery 或 k6 进行负载测试。

javascript
// artillery.config.js module.exports = { config: { target: 'http://localhost:3000', phases: [ { duration: 60, arrivalRate: 10 }, ], }, scenarios: [ { flow: [ { get: { url: '/users' } }, ], }, ], };

2. 响应时间测试

确保 API 响应时间在可接受范围内。

typescript
it('should respond within 200ms', async () => { const start = Date.now(); await request(app.getHttpServer()).get('/users'); const duration = Date.now() - start; expect(duration).toBeLessThan(200); });

代码质量最佳实践

1. 代码风格

使用 ESLint 和 Prettier 保持代码风格一致。

bash
npm run lint npm run format

2. 类型安全

充分利用 TypeScript 的类型系统。

typescript
interface CreateUserDto { name: string; email: string; }

3. 错误处理

统一错误处理机制。

typescript
@Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { // 统一错误处理逻辑 } }

4. 日志记录

使用 NestJS Logger 进行结构化日志记录。

typescript
import { Logger } from '@nestjs/common'; export class UsersService { private readonly logger = new Logger(UsersService.name); async findAll() { this.logger.log('Finding all users'); // 业务逻辑 } }

5. 环境配置

使用 @nestjs/config 管理环境变量。

typescript
import { ConfigService } from '@nestjs/config'; export class UsersService { constructor(private configService: ConfigService) { const apiKey = this.configService.get('API_KEY'); } }

6. 文档化

使用 Swagger 生成 API 文档。

typescript
import { ApiTags, ApiOperation } from '@nestjs/swagger'; @ApiTags('users') @Controller('users') export class UsersController { @Get() @ApiOperation({ summary: 'Get all users' }) findAll() { return this.usersService.findAll(); } }

总结

NestJS 测试和最佳实践提供了:

  • 完整的测试支持框架
  • 灵活的测试工具和实用程序
  • 清晰的测试组织结构
  • 高质量的代码标准
  • 良好的开发体验

掌握测试和最佳实践是构建高质量 NestJS 应用程序的关键。通过全面的测试覆盖和遵循最佳实践,可以确保应用程序的可靠性、可维护性和可扩展性。测试不仅仅是质量保证,更是开发过程中的重要组成部分。

标签:NestJS