本文属于 NestJS BBS 实战教程,共 11 节,建议按顺序学习。
1. 这节课要实现的功能是什么
- 用单元测试保护关键业务规则。
- 用 E2E 测试覆盖关键使用路径。
- 建立可交付能力:构建、测试、容器化。
2. 这节课涉及的 NestJS 知识点有哪些(先讲概念)
2.1 测试分层怎么理解
- 单元测试:测“某个类的业务逻辑”;
- E2E 测试:测“模块组合后是否按预期工作”。
2.2 为什么要在 Nest 里 Mock Provider
Nest 的依赖注入天生适合测试替换。你可以在测试模块里用 useValue 注入 mock 仓储,避免依赖真实外部环境。
2.3 @nestjs/testing 的核心价值
它能以接近生产的方式组装模块,让你的测试更贴近真实运行行为。
2.4 交付不仅是“代码写完”
真实项目里,至少要有:
npm run testnpm run test:e2enpm run build- Docker 运行一致性
3. 实际开发中这些 NestJS 知识点被如何使用(详细代码)
3.1 src/users/users.service.mock.spec.ts - Mock Provider 单元测试
import { ConflictException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { USERS_REPOSITORY } from './users.repository';
import { UsersService } from './users.service';
describe('UsersService (mocked repository)', () => {
it('throws conflict when email already exists', async () => {
const repo = {
findByEmail: jest.fn().mockResolvedValue({
id: 1,
username: 'alice',
email: 'alice@example.com',
passwordHash: 'hash',
role: 'user',
createdAt: new Date(),
}),
findAll: jest.fn().mockResolvedValue([]),
create: jest.fn(),
findById: jest.fn(),
updateRole: jest.fn(),
};
const moduleRef = await Test.createTestingModule({
providers: [
UsersService,
{ provide: USERS_REPOSITORY, useValue: repo },
],
}).compile();
const service = moduleRef.get(UsersService);
await expect(
service.create({ username: 'alice', email: 'alice@example.com', password: 'password123' }),
).rejects.toThrow(ConflictException);
});
});
3.2 test/auth.e2e-spec.ts - 认证链路测试
import { INestApplication, UnauthorizedException, ValidationPipe } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { AuthService } from '../src/auth/auth.service';
describe('Auth (e2e)', () => {
let app: INestApplication;
let authService: AuthService;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true }));
await app.init();
authService = app.get(AuthService);
});
it('registers a user and returns jwt token', async () => {
const result = await authService.register({ username: 'tom', email: 'tom@example.com', password: 'password123' });
expect(typeof result.accessToken).toBe('string');
});
it('rejects login with wrong password', async () => {
await expect(authService.validateUser({ email: 'tom@example.com', password: 'wrong-password' }))
.rejects.toThrow(UnauthorizedException);
});
});
3.3 test/app.e2e-spec.ts - 模块图可用性测试
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { AppController } from '../src/app.controller';
import { AppModule } from '../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
let appController: AppController;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true }));
await app.init();
appController = app.get(AppController);
});
it('resolves app controller through module graph', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
3.4 Dockerfile - 交付一致性
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]
3.5 本节落地价值
你现在拥有了“可验证、可构建、可部署”的完整闭环,这是真正工程化项目的门槛线。
4. 总结这节课知识点
- 你掌握了 Nest 的测试装配方式和 Provider Mock 思路。
- 你把质量保障从“手工验证”升级为“自动验证”。
- 你具备了从开发到交付的完整能力链。
5. 每节课分支
lesson-10-testing-delivery- 远程仓库:
https://github.com/Shiuwn/nestjs-tutorial.git