From nestjs-plugin
NestJS testing patterns: Test.createTestingModule for unit/integration, mocking providers, e2e tests with INestApplication + supertest, ORM mocking (TypeORM/Prisma/Mongoose), test fixtures. Use this skill to: - Write unit tests for services with mocked dependencies. - Write integration tests with real DI but mocked external services. - Write e2e tests for HTTP endpoints via supertest. - Mock repositories/Prisma client correctly. - Cover services and controllers to ≥80%. Do NOT use this skill for: - Test framework setup (Jest/Vitest config — usually already in place). - Frontend tests. - Load testing (out of QA scope).
How this skill is triggered — by the user, by Claude, or both
Slash command
/nestjs-plugin:nest-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
NestJS has a first-class testing module. The pattern: build a synthetic IoC container with the providers you want, override what you need, and exercise the system.
NestJS has a first-class testing module. The pattern: build a synthetic IoC container with the providers you want, override what you need, and exercise the system.
Default in Nest CLI is Jest. If the project uses Vitest, the patterns translate 1:1 — the imports change (@nestjs/testing works with both).
Test scripts in package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json"
}
}
E2E tests live in test/ (or apps/<app>/test/ in monorepo) and use a separate Jest config that picks up *.e2e-spec.ts.
// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
describe('UsersService', () => {
let service: UsersService;
let repo: jest.Mocked<Repository<User>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: {
findOne: jest.fn(),
find: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();
service = module.get(UsersService);
repo = module.get(getRepositoryToken(User));
});
it('finds a user by email', async () => {
const user = { id: '1', email: '[email protected]' } as User;
repo.findOne.mockResolvedValue(user);
expect(await service.findByEmail('[email protected]')).toBe(user);
expect(repo.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
});
it('returns null when not found', async () => {
repo.findOne.mockResolvedValue(null);
expect(await service.findByEmail('[email protected]')).toBeNull();
});
});
Key points:
getRepositoryToken(Entity) as the provider token — TypeORM's @InjectRepository resolves to this.jest.Mocked<Repository<User>> for autocomplete and type safety on mockResolvedValue / mockRejectedValue.import { mockDeep, DeepMockProxy } from 'jest-mock-extended';
import { PrismaClient } from '@prisma/client';
let prisma: DeepMockProxy<PrismaClient>;
beforeEach(async () => {
prisma = mockDeep<PrismaClient>();
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: PrismaService, useValue: prisma },
],
}).compile();
service = module.get(UsersService);
});
it('creates a user', async () => {
const created = { id: '1', email: '[email protected]' };
prisma.user.create.mockResolvedValue(created as any);
const result = await service.create({ email: '[email protected]', password: 'secret' });
expect(result).toEqual(created);
});
jest-mock-extended (pnpm add -D jest-mock-extended) provides typed deep mocks — Prisma's nested API (prisma.user.create, prisma.user.findMany) gets full type info.
import { getModelToken } from '@nestjs/mongoose';
const userModelMock = {
findOne: jest.fn().mockReturnValue({ exec: jest.fn() }),
create: jest.fn(),
// ...
};
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: getModelToken(User.name), useValue: userModelMock },
],
}).compile();
});
Mongoose's chained API (.findOne().exec()) makes mocks fiddly — return { exec: jest.fn() } from each query method.
For controllers with services, often you want real DI through the controller-service-repository chain but mock the database:
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [
UsersService,
{ provide: getRepositoryToken(User), useValue: repoMock },
{ provide: MailerService, useValue: { send: jest.fn().mockResolvedValue(undefined) } },
],
})
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true })
.compile();
.overrideGuard(...).useValue(...) is the cleanest way to bypass auth in tests.
// test/users.e2e-spec.ts
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;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(MailerService).useValue({ send: jest.fn() })
.compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
await app.init();
});
afterAll(async () => {
await app.close();
});
it('POST /users → 201', async () => {
const res = await request(app.getHttpServer())
.post('/users')
.send({ email: '[email protected]', password: 'longenough' })
.expect(201);
expect(res.body).toMatchObject({ email: '[email protected]' });
expect(res.body).not.toHaveProperty('passwordHash');
});
it('POST /users with extra field → 400', async () => {
await request(app.getHttpServer())
.post('/users')
.send({ email: '[email protected]', password: 'longenough', isAdmin: true })
.expect(400); // forbidNonWhitelisted catches this
});
});
Use app.getHttpServer() (not app.listen()) — supertest binds to a random port itself.
For database, prefer:
beforeAll.beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(getDataSourceToken())
.useFactory({
factory: async () => {
const ds = new DataSource({
type: 'sqlite',
database: ':memory:',
entities: [User, Order],
synchronize: true, // OK in tests
});
await ds.initialize();
return ds;
},
})
.compile();
// ...
});
For repeated entities, factory functions:
// test/factories/user.factory.ts
import { faker } from '@faker-js/faker';
import { User } from '../../src/users/entities/user.entity';
export const userFactory = (overrides: Partial<User> = {}): User => ({
id: faker.string.uuid(),
email: faker.internet.email(),
passwordHash: '$argon2id$...',
metadata: {},
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
} as User);
Use userFactory({ email: '[email protected]' }) in tests to override what matters and get reasonable defaults for the rest.
Target ≥80% for services and controllers. These hold business logic.
Don't measure coverage on:
*.module.ts) — pure configuration.main.ts — bootstrap.Configure Jest to exclude:
// package.json
{
"jest": {
"collectCoverageFrom": [
"src/**/*.{ts,js}",
"!src/**/*.module.ts",
"!src/**/*.dto.ts",
"!src/**/*.entity.ts",
"!src/main.ts"
]
}
}
The qa-engineer agent has a hard 3-attempt cap on fixing failing tests. If a test is fundamentally fragile after 3 attempts, mark it it.skip(...) with a comment explaining why and report in the QA summary. Don't iterate past the cap.
beforeEach reset.localhost:3000 — use app.getHttpServer() so each test creates its own port.body.passwordHash not being there as the only protection — also test that the endpoint excludes it via DTO/serializer.expect(spy).toHaveBeenCalled() without .toHaveBeenCalledWith(...) — half a test.npx claudepluginhub aratkruglik/claude-sdlc --plugin nestjs-pluginGuides writing and debugging TypeScript/NestJS unit tests with Jest, DeepMocked createMock, and in-memory databases. Activates on .spec.ts files or testing workflows.
Tests NestJS apps with createTestingModule, jest mocks, overrideProvider, and supertest for unit, integration, and e2e tests.
Expert in testing strategies for React, Next.js, and NestJS apps. Covers unit, integration, and E2E tests using Jest, Vitest, React Testing Library, Supertest, Playwright, and Cypress.