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

What are the NestJS testing and best practices?

2月17日 22:36

NestJS Testing Overview

NestJS provides comprehensive testing support, including unit testing, integration testing, and end-to-end testing. The testing framework is based on Jest and provides rich testing tools and utilities.

Testing Types

1. Unit Testing

Unit tests are used to test the behavior of a single function, class, or module, typically without involving external dependencies.

Service Unit Test Example

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

Integration tests are used to test the interaction between multiple components, typically involving databases, external services, etc.

Controller Integration Test Example

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. End-to-End Testing

E2E tests simulate real user scenarios, testing the entire application flow.

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(); }); });

Testing Tools and Utilities

1. Test.createTestingModule()

Create a test module for configuring the test environment.

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

2. Mock Providers

Use mock objects to replace real providers.

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

3. Jest Functions

Use Jest's mock functionality.

typescript
jest.fn() // Create mock function jest.mock() // Module mock jest.spyOn() // Spy on object methods mockResolvedValue() // Set return value mockRejectedValue() // Set rejection value mockReturnValue() // Set synchronous return value mockReturnValueOnce() // Set one-time return value

Best Practices

1. Test Organization

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. Test Naming Conventions

  • Use describe to group related tests
  • Use it or test to describe individual test cases
  • Test names should clearly describe what is being tested
typescript
describe('UsersService', () => { describe('create', () => { it('should create a new user', async () => { // Test logic }); it('should throw an error if email already exists', async () => { // Test logic }); }); });

3. Test Isolation

Each test should run independently without relying on state from other tests.

typescript
beforeEach(() => { // Reset state before each test }); afterEach(() => { // Clean up after each test });

4. Test Coverage

Use Jest's coverage feature to ensure code quality.

bash
npm run test:cov

Configure in 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 External Dependencies

For external dependencies like databases, APIs, use mocks to avoid actual calls.

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

6. Test Asynchronous Code

Properly handle testing of asynchronous code.

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

7. Test Edge Cases

Ensure tests cover various edge cases and error scenarios.

typescript
describe('findOne', () => { it('should return user when found', async () => { // Normal case }); it('should throw NotFoundException when user not found', async () => { // User not found case }); it('should throw BadRequestException when id is invalid', async () => { // Invalid ID case }); });

8. Use Test Database

Use a test database in integration tests to avoid affecting production data.

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

Performance Testing

1. Load Testing

Use tools like Artillery or k6 for load testing.

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

2. Response Time Testing

Ensure API response times are within acceptable ranges.

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); });

Code Quality Best Practices

1. Code Style

Use ESLint and Prettier to maintain consistent code style.

bash
npm run lint npm run format

2. Type Safety

Fully utilize TypeScript's type system.

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

3. Error Handling

Unified error handling mechanism.

typescript
@Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { // Unified error handling logic } }

4. Logging

Use NestJS Logger for structured logging.

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

5. Environment Configuration

Use @nestjs/config to manage environment variables.

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

6. Documentation

Use Swagger to generate API documentation.

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(); } }

Summary

NestJS testing and best practices provide:

  • Comprehensive testing support framework
  • Flexible testing tools and utilities
  • Clear test organization structure
  • High-quality code standards
  • Excellent development experience

Mastering testing and best practices is key to building high-quality NestJS applications. Through comprehensive test coverage and following best practices, you can ensure the reliability, maintainability, and scalability of your applications. Testing is not just quality assurance, but an important part of the development process.

标签:NestJS