Nestjs Testing Expert

shipshitdev/skills/skills/nestjs-testing-expert

作者 shipshitdev82fb91a7ce71無授權條款38 個星標收錄於 2026年10月9日更新於 2026年10月8日儲存庫昨天更新

Writes NestJS Vitest tests — testing modules, provider mocks, service/controller specs, Supertest e2e. Use for any test touching a NestJS service, controller, guard, or endpoint.

AI 產生的概覽

指導為 NestJS 的服務、控制器、守衛與 HTTP 端點撰寫 Vitest 單元、整合與端對端測試。

功能
提供在 NestJS 專案中建立 Vitest 測試套件的說明與程式碼模式:測試模組設定、提供者模擬與覆寫、服務與控制器測試,以及以 Supertest 進行的 HTTP 流程。內容涵蓋使用 SWC 處理裝飾器中繼資料的 Vitest 設定、資料庫策略與清理做法。產出的是測試程式碼與設定指引,本身不執行任何動作。
適用情境
適用於為 NestJS 應用程式撰寫或設定單元、整合或端對端測試,包括測試模組、覆寫提供者或守衛,以及針對已啟動的 Nest 應用程式測試 HTTP 端點。
執行需求
僅為說明文件,不含指令碼。假定已有 NestJS 專案與 Vitest,並需要 unplugin-swc、@swc/core、@golevelup/ts-vitest、supertest、@types/supertest 等套件;整合與端對端測試還需要資料庫與已啟動的應用程式。

NestJS Testing Expert

Build reliable Vitest suites for NestJS modules, services, controllers, and HTTP endpoints.

Scope

This skill owns NestJS mechanics: testing modules, provider wiring, and request-level end-to-end flows. For framework-agnostic questions — which level a behavior belongs at, what a coverage number means, how to design a test that survives refactoring, how to kill a flake — use testing-expert.

When to Use

  • Writing unit, integration, or end-to-end tests for a NestJS application
  • Setting up a testing module, overriding providers, or faking a database
  • Testing controllers, guards, interceptors, or pipes
  • Exercising HTTP endpoints against a booted Nest application

Levels in a Nest Application

  • Unit — a service or provider in isolation, collaborators replaced with test doubles. No module graph beyond the providers under test.
  • Integration — a module compiled with its real providers, external systems faked. Proves the wiring and the contracts between layers.
  • End-to-end — a booted application driven over HTTP. Proves the full request path: pipes, guards, controller, service, persistence.

Vitest Setup for NestJS

Nest's dependency injection reads decorator metadata (emitDecoratorMetadata). Vitest transforms with esbuild, which does not emit it, so constructor injection silently resolves to undefined. Compile with unplugin-swc, which does emit decorator metadata.

bash
bun add -D vitest unplugin-swc @swc/core @golevelup/ts-vitest supertest @types/supertest
typescript
// vitest.config.ts — unit and integration specsimport swc from 'unplugin-swc';import { defineConfig } from 'vitest/config';
export default defineConfig({  test: {    globals: true,    root: './',    environment: 'node',    include: ['src/**/*.spec.ts'],    testTimeout: 10_000,  },  plugins: [    swc.vite({      module: { type: 'es6' },    }),  ],});

Keep end-to-end specs in a separate config so bun run test stays fast and bun run test:e2e boots the application:

typescript
// vitest.config.e2e.tsimport swc from 'unplugin-swc';import { defineConfig } from 'vitest/config';
export default defineConfig({  test: {    globals: true,    root: './',    environment: 'node',    include: ['test/**/*.e2e-spec.ts'],    testTimeout: 30_000,  },  plugins: [swc.vite({ module: { type: 'es6' } })],});
json
{  "scripts": {    "test": "vitest run",    "test:e2e": "vitest run --config vitest.config.e2e.ts",    "test:cov": "vitest run --coverage"  }}

Options from older runner configs map to Vitest one to one: path aliases move from moduleNameMapper to resolve.alias (or the vite-tsconfig-paths plugin), testEnvironment becomes test.environment, transform is replaced by the SWC plugin, and per-suite timeouts use test.testTimeout or vi.setConfig({ testTimeout }). Import describe, it, expect, vi, and the lifecycle hooks from vitest explicitly when globals is off. Browser-driven end-to-end flows belong to Playwright; Supertest here covers HTTP against a booted Nest app.

For auto-mocking whole providers, createMock from @golevelup/ts-vitest returns a deep mock typed to the class:

typescript
import { createMock, DeepMocked } from '@golevelup/ts-vitest';

Service Specs

Compile a testing module with the subject real and every collaborator supplied explicitly. Injection tokens come from whatever integration provides them — an ORM's token helper, a class reference, or a custom token constant.

typescript
import { Test, TestingModule } from '@nestjs/testing';import { afterEach, beforeEach, describe, expect, it, vi, type Mocked } from 'vitest';
describe('UsersService', () => {  let service: UsersService;  let repository: Mocked<UsersRepository>;
  beforeEach(async () => {    const module: TestingModule = await Test.createTestingModule({      providers: [        UsersService,        { provide: UsersRepository, useValue: createUsersRepositoryMock() },      ],    }).compile();
    service = module.get(UsersService);    repository = module.get(UsersRepository);  });
  afterEach(() => {    vi.resetAllMocks();  });
  it('returns only the active users of the requested organization', async () => {    const expected = [{ id: '1', organization: 'org1' }];    repository.find.mockResolvedValue(expected);
    const result = await service.findAll('org1');
    expect(result).toEqual(expected);    expect(repository.find).toHaveBeenCalledWith({      organization: 'org1',      isDeleted: false,    });  });});

Assert on the arguments the collaborator received when they encode a business rule — the isDeleted: false filter above is the behavior, not an implementation detail.

Controller Specs

Register the controller, stub its service, and verify only the controller's own job: argument extraction, delegation, and response shaping. Business rules belong to the service spec.

typescript
describe('UsersController', () => {  let controller: UsersController;  let service: Mocked<UsersService>;
  beforeEach(async () => {    const module: TestingModule = await Test.createTestingModule({      controllers: [UsersController],      providers: [{ provide: UsersService, useValue: createUsersServiceMock() }],    }).compile();
    controller = module.get(UsersController);    service = module.get(UsersService);  });
  it('passes the organization from the request through to the service', async () => {    const expected = [{ id: '1', email: '[email protected]' }];    service.findAll.mockResolvedValue(expected);
    const result = await controller.findAll('org1');
    expect(result).toEqual(expected);    expect(service.findAll).toHaveBeenCalledWith('org1');  });});

Overriding Providers

Compile the real module and swap only what must not run for real. overrideProvider and overrideGuard keep the rest of the graph authentic, so the test still proves the wiring.

typescript
const module: TestingModule = await Test.createTestingModule({  imports: [UsersModule],})  .overrideProvider(MailerService)  .useValue(mailerMock)  .overrideGuard(AuthGuard)  .useValue({ canActivate: () => true })  .compile();

Override the guard only in tests about something else. Authorization itself deserves end-to-end tests that run the real guard.

Integration Tests

Boot the application, keep persistence real against a disposable database, and reset state between tests.

typescript
describe('Users (integration)', () => {  let app: INestApplication;
  beforeAll(async () => {    const moduleFixture: TestingModule = await Test.createTestingModule({      imports: [AppModule],    }).compile();
    app = moduleFixture.createNestApplication();    await app.init();  });
  afterAll(async () => {    await app.close();  });
  beforeEach(async () => {    await resetDatabase();  });
  it('persists a created user and returns it on read', async () => {    const created = await request(app.getHttpServer())      .post('/api/users')      .send({ email: '[email protected]', name: 'Test' })      .expect(201);
    const read = await request(app.getHttpServer())      .get(`/api/users/${created.body.id}`)      .expect(200);
    expect(read.body.email).toBe('[email protected]');  });});

Close the application in afterAll. A leaked Nest application holds its connection pool open and hangs the Vitest run.

End-to-End Tests

Same boot, driven as a real client — authentication included.

typescript
describe('Users API (e2e)', () => {  let app: INestApplication;  let authToken: string;
  beforeAll(async () => {    const moduleFixture: TestingModule = await Test.createTestingModule({      imports: [AppModule],    }).compile();
    app = moduleFixture.createNestApplication();    await app.init();
    authToken = await signInTestUser(app);  });
  afterAll(async () => {    await app.close();  });
  it('rejects an unauthenticated list request', () =>    request(app.getHttpServer()).get('/api/users').expect(401));
  it('returns the caller organization users when authenticated', () =>    request(app.getHttpServer())      .get('/api/users')      .set('Authorization', `Bearer ${authToken}`)      .expect(200)      .expect((res) => {        expect(Array.isArray(res.body)).toBe(true);      }));});

A shared signInTestUser helper keeps the token flow in one place and out of every spec.

Database Strategy

ApproachFitsCost
Repository test doubleUnit specsFastest; proves no SQL or schema
In-memory or embedded engineIntegration specsFast; behavior can drift from production
Disposable container per runIntegration and e2e specsSlowest; highest fidelity

Reset between tests by truncating or rolling back a transaction rather than recreating the schema — schema rebuilds dominate suite runtime.

Tips

  • Give each spec its own module compilation; a shared one leaks state between tests.
  • Reset mocks in afterEach so a stub set in one test cannot satisfy the next.
  • Boot the application once per describe block and reset data per test — booting per test is the usual cause of a slow Nest suite.
  • Keep tests deterministic: freeze the clock (vi.useFakeTimers() with vi.setSystemTime) and pin the timezone rather than asserting on the real one.
  • Restore spies with vi.restoreAllMocks() in afterEach when a spec uses vi.spyOn on shared objects.

Checklist

  • Clear arrange/act/assert structure
  • Subject real, collaborators explicit — never the reverse
  • Error and unauthorized paths covered, not just the happy path
  • Application closed and mocks reset in teardown
  • Fast to run

來源與署名

來源:shipshitdev/skills位於skills/nestjs-testing-expert提交82fb91a

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架