跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 基于 NestJS 12 本机实测:Nest 12 脚手架单测用 vitest@nestjs/testing + vitest 4),下方单测/e2e 输出均为真实运行结果;Docker 部分未在本机跑(无容器环境),为标准多阶段写法。

测试与部署:把 Nest 服务调到「敢上线」

一句话:Nest 的依赖注入既是架构优点,也是测试利器——@nestjs/testingTestingModule 让你在测试里任意「换实现」,单测测逻辑、e2e 测整条 HTTP 链路;部署则不过「nest build 出 JS + 容器跑 node dist/main」两步。

1. 测试三层定位

测什么用什么
单元测试单个 service/pipe/guard 的逻辑TestingModule + 把依赖 mock 掉
e2e 测试整条「HTTP → 路由 → DTO → service」supertest 对着真实 app 发请求
契约/集成与外部 DB/第三方常用 Testcontainers / 真库的集成测试

Nest 12 脚手架已配好:单测 npm run test(vitest)、e2e npm run test:e2evitest.config.e2e.ts)。老教程里 jest 的写法在新脚手架被 vitest 取代——API 概念通用,跑之前看下你工程的配置文件。

2. 单元测试:TestingModule + mock 依赖

Nest 测试的核心是「不起完整应用,只编译你要测的那一小块 DI 图」:

// cats.service.spec.ts
import { Test } from '@nestjs/testing';
import { CatsService } from './cats.service.js';
import { getRepositoryToken } from '@nestjs/typeorm'; // 若用 TypeORM

describe('CatsService', () => {
let service: CatsService;

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
CatsService,
{
provide: getRepositoryToken(Cat), // 把 Repository 换成内存 fake
useValue: { find: () => [{ id: 1, name: 'Tom' }], save: jest.fn?.() ?? vi.fn() },
},
],
}).compile();

service = moduleRef.get(CatsService);
});

it('findAll 返回列表', async () => {
await expect(service.findAll()).resolves.toHaveLength(1);
});
});

要点:

  • Test.createTestingModule({ providers }) 不用真连 DB / 真起 Redis——把外部依赖(Repository、HttpService、Redis client)用 useValue 注入假实现,测的就是你自己的逻辑;
  • .overrideProvider(X).useValue(fake) / .overrideGuard(...) 也是常用姿势,适合「测 controller 时把 service mock 掉」;
  • 想测「Guard / Pipe 的独立逻辑」,直接 new RolesGuard(reflector) 单测即可。

脚手架自带的单测(src/app.controller.spec.ts)真实运行输出:

> npm run test
> vitest run
RUN v4.1.11 /private/tmp/nestjs-guide
Test Files 1 passed (1)
Tests 1 passed (1)
Duration 641ms

3. e2e 测试:对着真实 app 打 HTTP

e2e 是「把整条链路(含 ValidationPipe、Guard)真实走一遍」:

// test/cats.e2e-spec.ts
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest'; // 或 import * as request
import { AppModule } from '../src/app.module.js';

describe('CatsController (e2e)', () => {
let app: INestApplication;

beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => { await app.close(); });

it('POST /cats 带非法参数 → 400', async () => {
await request(app.getHttpServer())
.post('/cats').send({ name: 'Tom', age: 99 })
.expect(400);
});

it('GET / → Hello World!', async () => {
await request(app.getHttpServer()).get('/').expect(200, 'Hello World!');
});
});

脚手架自带 e2e(test/app.e2e-spec.ts)真实运行输出:

> npm run test:e2e
> vitest run --config ./vitest.config.e2e.ts
RUN v4.1.11 /private/tmp/nestjs-guide
Test Files 1 passed (1)
Tests 1 passed (1)
Duration 877ms

e2e 最大的价值是把 §3 的 DTO 白名单、§4 的 Guard 401 一起锁进测试——以后谁改了校验/鉴权,CI 立刻红。

4. 生产构建与运行

npm run build # nest build → 编译到 dist/
npm run start:prod # node dist/main —— 就是脚手架给的生产启动脚本

产物就是 dist/ 下一堆纯 JS + 你的 node_modules 运行时依赖。部署只要把这两样搬到服务器/镜像里。

Docker 多阶段(标准姿势,未在本机跑)

# ---- build 阶段 ----
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ---- run 阶段:只要产物 + 生产依赖 ----
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

关键:run 镜像不装 devDependencies、不带源码,只带 dist + 生产依赖,镜像小、攻击面小。

配置与环境

  • 别把 Key 打进镜像:用环境变量(process.env)+ @nestjs/configConfigModule.forRoot({ isGlobal: true }).env
  • 容器里设 PORT 即可被 dist/main.js 读取(process.env.PORT ?? 3000);
  • 上健康检查:@nestjs/terminus 暴露 /health(对应 Docker/K8s 的 liveness)。

5. 上线前 checklist

  1. npm run build 干净通过;
  2. npm run test + test:e2e 全绿(把鉴权/校验用例写进去);
  3. npm run lint(脚手架用 oxlint)无错误;
  4. DB 迁移用 migration 而不是 synchronize,部署前先跑迁移;
  5. Secret 全部走环境变量,NODE_ENV=production
  6. 容器只带 dist + 生产依赖,配 /health 健康检查与优雅停机(enableShutdownHooks,呼应篇 1)。

小结:Nest 一个「域」的完整姿态

Module(组装)
→ Controller(薄壳,只做路由)
→ DTO + ValidationPipe(参数进门前校验)
→ Guard(鉴权/权限,进门前拦截)
→ Service(业务,可注入 Repository / Prisma / 其它域)
→ 测试:单测 mock 依赖 + e2e 打真实 HTTP
→ 部署:nest build → dist → Docker(node dist/main) + env + health

关联