nestjs-jest-testing-excellence
Aplica padrões de excelência em testes com NestJS e Jest seguindo Clean Architecture, DDD e boas práticas de testabilidade. Use quando criar ou refatorar testes unitários, de integração ou E2E em projetos NestJS.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for implementing high-standard testing in NestJS applications using Jest. It enforces Clean Architecture and Domain-Driven Design (DDD) principles to ensure codebases are testable, maintainable, and robust. By automating the generation of unit, integration, and E2E tests with optimized patterns like Fake Builders and custom matchers, it helps developers consistently achieve over 80% code coverage while significantly reducing manual boilerplate and improving test execution speed.
Use Cases
- Generating unit tests for domain entities and value objects using the Fake Builder pattern to ensure business logic isolation and realistic data simulation.
- Implementing integration tests for infrastructure layers using Sequelize helpers to validate database interactions, ORM mapping, and repository patterns.
- Creating comprehensive E2E test suites with custom Supertest extensions to validate full API workflows, authentication, and authorization constraints.
- Refactoring legacy test suites to improve maintainability and performance by adopting SWC transformers and optimized Jest configurations.
- Enforcing architectural boundaries and testability-by-design, ensuring that domain logic remains decoupled from frameworks like NestJS.
| name | "NestJS + Jest Testing Excellence" |
|---|---|
| description | "Aplica padrões de excelência em testes com NestJS e Jest seguindo Clean Architecture, DDD e boas práticas de testabilidade. Use quando criar ou refatorar testes unitários, de integração ou E2E em projetos NestJS." |
NestJS + Jest Testing Excellence Skill
Objetivo
Esta Skill orienta Claude Code a aplicar padrões de excelência em testes para projetos NestJS + Jest, seguindo princípios de Clean Architecture, Domain-Driven Design e testabilidade por design. Baseada em análise técnica de projeto de referência com cobertura obrigatória de 80%.
Quando usar
Ative esta Skill quando:
- Criar novos testes unitários, de integração ou E2E em NestJS
- Refatorar testes existentes para melhorar qualidade e manutenibilidade
- Implementar padrões de test fixtures e builders
- Configurar ambiente de testes com Jest
- Criar custom matchers específicos do domínio
- Organizar arquitetura testável com separação de camadas
- Implementar repositórios in-memory para testes rápidos
- Definir estratégias de teste por camada (Domain, Application, Infrastructure)
Entradas esperadas
arquivo_ou_modulo: Código-fonte que precisa de testes ou testes existentes que precisam de melhoriatipo_teste: Especificar se é unit (.spec.ts), integration (.int-spec.ts) ou e2e (.e2e-spec.ts)camada: Informar qual camada está sendo testada (domain, application, infrastructure, nest-modules)contexto_projeto: Estrutura de diretórios e padrões já existentes no projeto (opcional)
Saídas esperadas
- Código de teste seguindo padrões de excelência
- Implementação de test fixtures e builders quando apropriado
- Configuração de helpers de setup/teardown se necessário
- Explicação da estratégia de teste aplicada
- Recomendações de melhorias adicionais quando aplicável
Princípios Fundamentais
1. Arquitetura Testável por Design
SEMPRE aplique separação de camadas:
src/
├── core/ # Domain & Application (Business Logic)
│ ├── [modulo]/
│ │ ├── domain/ # Entidades, Value Objects, Repository Interfaces
│ │ ├── application/ # Use Cases (lógica de aplicação)
│ │ └── infra/ # Implementações de infraestrutura
│ │ ├── db/sequelize/ # ORM para produção
│ │ └── db/in-memory/ # In-Memory para testes
│ └── shared/
│ ├── domain/ # Building blocks do domínio
│ ├── application/ # Serviços base
│ └── infra/ # Infraestrutura compartilhada
│
└── nest-modules/ # Infrastructure Layer (Framework)
└── [modulo]-module/
├── [modulo].controller.ts
├── [modulo].providers.ts
└── __tests__/
Regras de ouro:
- Domain layer: ZERO imports de
@nestjs/*ou frameworks - Defina interfaces de repositório na camada de domínio
- Implemente versões in-memory para testes unitários
- Use injeção de dependências para trocar implementações
2. Três Níveis de Teste
Unit Tests (.spec.ts):
- Testam lógica de negócio isolada
- Usam repositórios in-memory
- Execução instantânea (sem I/O)
- Colocação: ao lado do código testado em
__tests__/
Integration Tests (.int-spec.ts):
- Testam integração com infraestrutura real (banco, ORM)
- Usam banco de dados de teste
- Validam mapeamento ORM e queries
- Colocação: em
infra/db/[orm]/__tests__/
E2E Tests (.e2e-spec.ts):
- Testam fluxos HTTP completos
- Usam aplicação NestJS inicializada
- Validam autenticação, autorização, contratos de API
- Colocação: diretório
test/na raiz do projeto
Padrões Obrigatórios
Pattern 1: Mock de Validação em Testes de Construção
Quando usar: Testes de entidades de domínio
Aplique sempre:
describe('Entity Without Validator Unit Tests', () => {
beforeEach(() => {
Entity.prototype.validate = jest
.fn()
.mockImplementation(Entity.prototype.validate);
});
test('constructor of entity', () => {
let entity = new Entity({ name: 'Test' });
expect(entity.id).toBeInstanceOf(EntityId);
expect(entity.name).toBe('Test');
expect(entity.created_at).toBeInstanceOf(Date);
});
});
Benefícios:
- Separa testes de construção dos testes de validação
- Evita falhas em cascata quando validação muda
- Execução mais rápida
Pattern 2: Spy em Métodos do Repositório
Quando usar: Testes de use cases
Aplique sempre:
describe('CreateEntityUseCase Unit Tests', () => {
let useCase: CreateEntityUseCase;
let repository: EntityInMemoryRepository;
beforeEach(() => {
repository = new EntityInMemoryRepository();
useCase = new CreateEntityUseCase(repository);
});
it('should create an entity', async () => {
const spyInsert = jest.spyOn(repository, 'insert');
let output = await useCase.execute({ name: 'test' });
expect(spyInsert).toHaveBeenCalledTimes(1);
expect(output).toStrictEqual({
id: repository.items[0].id.id,
name: 'test',
created_at: repository.items[0].created_at,
});
});
});
Benefícios:
- Verifica comportamento interno sem quebrar encapsulamento
- Valida número de chamadas (previne duplicações)
- Permite verificar argumentos passados
Pattern 3: test.each para Cobertura de Edge Cases
Quando usar: Validação de múltiplos cenários
Aplique sempre:
test.each([
{ input: null, expected: 'default' },
{ input: undefined, expected: 'default' },
{ input: '', expected: 'default' },
{ input: 'valid', expected: 'valid' },
{ input: 0, expected: 'default' },
{ input: -1, expected: 'default' },
{ input: {}, expected: 'default' },
])('input %p should return %p', ({ input, expected }) => {
expect(processInput(input as any)).toBe(expected);
});
Benefícios:
- Testa múltiplos casos com código mínimo
- Documentação clara de comportamento esperado
- Fácil adicionar novos casos
Pattern 4: Fake Builder Pattern
Quando usar: Criação de dados de teste para entidades
Implemente sempre:
export class EntityFakeBuilder<TBuild = any> {
private _id: PropOrFactory<EntityId> | undefined = undefined;
private _name: PropOrFactory<string> = (_index) => this.chance.word();
private _description: PropOrFactory<string | null> = (_index) =>
this.chance.paragraph();
private _created_at: PropOrFactory<Date> | undefined = undefined;
private countObjs: number;
static anEntity() {
return new EntityFakeBuilder<Entity>();
}
static theEntities(countObjs: number) {
return new EntityFakeBuilder<Entity[]>(countObjs);
}
withName(valueOrFactory: PropOrFactory<string>) {
this._name = valueOrFactory;
return this;
}
withDescription(valueOrFactory: PropOrFactory<string | null>) {
this._description = valueOrFactory;
return this;
}
build(): TBuild {
const entities = new Array(this.countObjs)
.fill(undefined)
.map((_, index) => {
const entity = new Entity({
id: !this._id ? undefined : this.callFactory(this._id, index),
name: this.callFactory(this._name, index),
description: this.callFactory(this._description, index),
created_at: !this._created_at
? undefined
: this.callFactory(this._created_at, index),
});
entity.validate();
return entity;
});
return this.countObjs === 1
? (entities[0] as any)
: (entities as any);
}
private callFactory(factoryOrValue: PropOrFactory<any>, index: number) {
return typeof factoryOrValue === 'function'
? factoryOrValue(index)
: factoryOrValue;
}
}
Uso:
// Criar uma entidade com valores padrão
const entity = Entity.fake().anEntity().build();
// Criar 16 entidades com nome específico
const entities = Entity.fake()
.theEntities(16)
.withName('Test')
.withDescription(null)
.build();
// Criar com factory function dinâmica
const entities = Entity.fake()
.theEntities(16)
.withName((index) => `Test ${index}`)
.withCreatedAt((index) => new Date(baseDate.getTime() + index))
.build();
Benefícios:
- API fluente e expressiva
- Valores padrão razoáveis (usando Chance.js)
- Suporte para criação em massa
- Suporte para factory functions (valores dinâmicos)
Pattern 5: Helper setupSequelize para Lifecycle
Quando usar: Testes de integração com banco de dados
Implemente sempre:
// src/core/shared/infra/testing/helpers.ts
export function setupSequelize(options: SequelizeOptions = {}) {
let _sequelize: Sequelize;
beforeAll(async () => {
_sequelize = new Sequelize({
...Config.db(),
...options,
});
});
beforeEach(async () => await _sequelize.sync({ force: true }));
afterAll(async () => await _sequelize.close());
return {
get sequelize() {
return _sequelize;
},
};
}
Uso:
describe('EntitySequelizeRepository Integration Test', () => {
let repository: EntitySequelizeRepository;
setupSequelize({ models: [EntityModel] });
beforeEach(async () => {
repository = new EntitySequelizeRepository(EntityModel);
});
it('should insert a new entity', async () => {
const entity = Entity.fake().anEntity().build();
await repository.insert(entity);
const entityCreated = await repository.findById(entity.id);
expect(entityCreated!.toJSON()).toStrictEqual(entity.toJSON());
});
});
Benefícios:
- Encapsula setup/teardown complexo
- Garante banco limpo a cada teste (force: true)
- Reutilizável em todos os testes de integração
- Previne vazamento de estado entre testes
Pattern 6: Helper startApp para E2E
Quando usar: Testes E2E
Implemente sempre:
// src/nest-modules/shared-module/testing/helpers.ts
export function startApp() {
let _app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider('UnitOfWork')
.useFactory({
factory: (sequelize: Sequelize) => {
return new UnitOfWorkSequelize(sequelize as any);
},
inject: [getConnectionToken()],
})
.compile();
const sequelize = moduleFixture.get<Sequelize>(getConnectionToken());
await sequelize.sync({ force: true });
_app = moduleFixture.createNestApplication();
applyGlobalConfig(_app);
await _app.init();
});
afterEach(async () => {
await _app?.close();
});
return {
get app() {
return _app;
},
};
}
Benefícios:
- Aplicação completa inicializada a cada teste
- Banco de dados limpo
- Configuração global aplicada
- Isolamento total entre testes
Pattern 7: Extensão do Supertest para Autenticação
Quando usar: Testes E2E com autenticação
Implemente sempre:
// src/nest-modules/shared-module/testing/supertest-extend.ts
import { INestApplication } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Test } from 'supertest';
Test.prototype.authenticate = function (
app: INestApplication,
forceAdmin = true,
) {
const jwtService = app.get(JwtService);
const token = jwtService.sign(
forceAdmin
? {
realm_access: {
roles: ['admin-catalog'],
},
}
: {},
);
return this.set('Authorization', `Bearer ${token}`);
};
Uso:
test('should return 401 when not authenticated', () => {
return request(app.app.getHttpServer())
.post('/entities')
.send({})
.expect(401);
});
test('should return 403 when not admin', () => {
return request(app.app.getHttpServer())
.post('/entities')
.authenticate(app.app, false)
.send({})
.expect(403);
});
test('should create successfully', async () => {
const res = await request(app.app.getHttpServer())
.post('/entities')
.authenticate(app.app)
.send({ name: 'test' })
.expect(201);
});
Benefícios:
- API fluente para autenticação
- Centraliza lógica de tokens
- Facilita testar diferentes permissões
Pattern 8: Test Fixtures para Cenários E2E
Quando usar: Testes E2E com múltiplos cenários
Implemente sempre:
// src/nest-modules/[modulo]-module/testing/[entity]-fixture.ts
export class CreateEntityFixture {
static arrangeForCreate() {
const faker = Entity.fake()
.anEntity()
.withName('Test')
.withDescription('description test');
return [
{
send_data: {
name: faker.name,
},
expected: {
name: faker.name,
description: null,
is_active: true,
},
},
{
send_data: {
name: faker.name,
description: faker.description,
is_active: false,
},
expected: {
name: faker.name,
description: faker.description,
is_active: false,
},
},
];
}
static arrangeInvalidRequest() {
return {
EMPTY: {
send_data: {},
expected: {
message: ['name should not be empty', 'name must be a string'],
statusCode: 422,
error: 'Unprocessable Entity',
},
},
NAME_NULL: {
send_data: { name: null },
expected: {
message: ['name should not be empty', 'name must be a string'],
statusCode: 422,
error: 'Unprocessable Entity',
},
},
};
}
static arrangeForEntityValidationError() {
const faker = Entity.fake().anEntity();
return {
NAME_TOO_LONG: {
send_data: {
name: faker.withName('a'.repeat(256)).name,
},
expected: {
statusCode: 422,
error: 'Unprocessable Entity',
message: ['name must be shorter than or equal to 255 characters'],
},
},
};
}
static keysInResponse = ['id', 'name', 'description', 'is_active', 'created_at'];
}
Uso:
describe('should return validation error', () => {
const invalidRequest = CreateEntityFixture.arrangeInvalidRequest();
const arrange = Object.keys(invalidRequest).map((key) => ({
label: key,
value: invalidRequest[key],
}));
test.each(arrange)('when body is $label', ({ value }) => {
return request(app.app.getHttpServer())
.post('/entities')
.authenticate(app.app)
.send(value.send_data)
.expect(422)
.expect(value.expected);
});
});
Benefícios:
- Centraliza definição de cenários
- Reutilizável entre testes
- Documentação clara de casos
- Fácil adicionar novos cenários
Configuração de Ambiente
Jest Configuration (jest.config.ts)
Aplique sempre:
import type { Config } from 'jest';
const config: Config = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\\..*spec\\.ts$',
transform: {
'^.+\\.(t|j)s$': '@swc/jest',
},
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: '../coverage',
coveragePathIgnorePatterns: [
'/node_modules/',
'.interface.ts',
'-interface.ts',
'shared/testing',
'shared-module/testing',
'validator-rules.ts',
'-fixture.ts',
'.input.ts',
'.d.ts',
],
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
testEnvironment: 'node',
setupFilesAfterEnv: ['./core/shared/infra/testing/expect-helpers.ts'],
coverageProvider: 'v8',
clearMocks: true,
};
export default config;
Pontos-chave:
- SWC transformer: 10x mais rápido que ts-jest
- Coverage threshold: 80% mínimo obrigatório
- Coverage ignore patterns: Exclui interfaces, fixtures, testing utilities
- clearMocks: Previne vazamento entre testes
- v8 coverage: Mais rápido e preciso
E2E Configuration (test/jest-e2e.config.ts)
Aplique sempre:
import { Config } from 'jest';
const config: Config = {
clearMocks: true,
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testEnvironment: 'node',
coverageProvider: 'v8',
testRegex: '.e2e-spec.ts$',
transform: {
'^.+\\.(t|j)s$': '@swc/jest',
},
setupFilesAfterEnv: ['./jest-setup.ts'],
};
export default config;
Setup E2E (test/jest-setup.ts):
import '../src/nest-modules/shared-module/testing/supertest-extend';
process.env.NODE_ENV = 'e2e';
Custom Jest Matchers
Implemente sempre que necessário:
// src/core/shared/infra/testing/expect-helpers.ts
import { Notification } from '../../domain/validators/notification';
import { ValueObject } from '../../domain/value-object';
expect.extend({
notificationContainsErrorMessages(
expected: Notification,
received: Array<string | { [key: string]: string[] }>,
) {
const every = received.every((error) => {
if (typeof error === 'string') {
return expected.errors.has(error);
} else {
return Object.entries(error).every(([field, messages]) => {
const fieldMessages = expected.errors.get(field) as string[];
return (
fieldMessages &&
fieldMessages.length &&
fieldMessages.every((message) => messages.includes(message))
);
});
}
});
return every
? { pass: true, message: () => '' }
: {
pass: false,
message: () =>
`The validation errors not contains ${JSON.stringify(
received,
)}. Current: ${JSON.stringify(expected.toJSON())}`,
};
},
toBeValueObject(expected: ValueObject, received: ValueObject) {
return expected.equals(received)
? { pass: true, message: () => '' }
: {
pass: false,
message: () =>
`The values object are not equal. Expected: ${JSON.stringify(
expected,
)} | Received: ${JSON.stringify(received)}`,
};
},
});
Uso:
// Validar notificações de erro
expect(entity.notification).notificationContainsErrorMessages([
{
name: ['name should not be empty', 'name must be a string'],
},
]);
// Comparar value objects
expect(id1).toBeValueObject(id2);
Estratégia de Teste por Camada
Domain Layer (core/[modulo]/domain/)
O que testar:
- Criação de entidades e agregados
- Métodos de negócio (changeName, activate, etc.)
- Validações de domínio
- Value Objects
- Invariantes de negócio
Como testar:
- Testes unitários (.spec.ts)
- Mock de validação em testes de construção
- test.each para edge cases
- Fake builders para criação de dados
Exemplo:
describe('Entity Unit Tests', () => {
describe('create command', () => {
test('should create with default values', () => {
const entity = Entity.create({ name: 'Test' });
expect(entity.id).toBeInstanceOf(EntityId);
expect(entity.name).toBe('Test');
expect(entity.notification.hasErrors()).toBe(false);
});
});
describe('changeName method', () => {
test('should change name', () => {
const entity = Entity.create({ name: 'Test' });
entity.changeName('Updated');
expect(entity.name).toBe('Updated');
});
test('should add validation error when invalid', () => {
const entity = Entity.create({ name: 'Test' });
entity.changeName('a'.repeat(256));
expect(entity.notification).notificationContainsErrorMessages([
{ name: ['name must be shorter than or equal to 255 characters'] },
]);
});
});
});
Application Layer (core/[modulo]/application/)
O que testar:
- Use cases com repositório in-memory
- Lógica de orquestração
- Transformação de input para output
- Tratamento de erros de negócio
Como testar:
- Testes unitários (.spec.ts)
- Spy em métodos do repositório
- Fake builders para dados
- Repositório in-memory
Exemplo:
describe('CreateEntityUseCase Unit Tests', () => {
let useCase: CreateEntityUseCase;
let repository: EntityInMemoryRepository;
beforeEach(() => {
repository = new EntityInMemoryRepository();
useCase = new CreateEntityUseCase(repository);
});
it('should throw error when invalid', async () => {
const input = { name: 't'.repeat(256) };
await expect(() => useCase.execute(input)).rejects.toThrowError(
'Entity Validation Error',
);
});
it('should create entity', async () => {
const spyInsert = jest.spyOn(repository, 'insert');
const output = await useCase.execute({ name: 'test' });
expect(spyInsert).toHaveBeenCalledTimes(1);
expect(output).toStrictEqual({
id: repository.items[0].id.id,
name: 'test',
created_at: repository.items[0].created_at,
});
});
});
Infrastructure Layer - Repositories (core/[modulo]/infra/)
O que testar:
- Operações CRUD com banco real
- Mapeamento ORM
- Queries e filtros
- Tratamento de erros (NotFound, etc.)
Como testar:
- Testes de integração (.int-spec.ts)
- setupSequelize helper
- Fake builders para dados
- Banco de dados de teste
Exemplo:
describe('EntitySequelizeRepository Integration Test', () => {
let repository: EntitySequelizeRepository;
setupSequelize({ models: [EntityModel] });
beforeEach(async () => {
repository = new EntitySequelizeRepository(EntityModel);
});
it('should insert new entity', async () => {
const entity = Entity.fake().anEntity().build();
await repository.insert(entity);
const found = await repository.findById(entity.id);
expect(found!.toJSON()).toStrictEqual(entity.toJSON());
});
it('should throw error when not found', async () => {
const entity = Entity.fake().anEntity().build();
await expect(repository.update(entity)).rejects.toThrow(
new NotFoundError(entity.id.id, Entity),
);
});
});
Infrastructure Layer - Controllers (nest-modules/)
O que testar:
- Injeção de dependências
- Wiring de módulos
- Controllers com TestingModule
Como testar:
- Testes de integração (.int-spec.ts)
- TestingModule do NestJS
- Test fixtures
Exemplo:
describe('EntitiesController Integration Tests', () => {
let controller: EntitiesController;
let repository: IEntityRepository;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot(),
DatabaseModule,
AuthModule,
EntitiesModule,
],
}).compile();
controller = module.get<EntitiesController>(EntitiesController);
repository = module.get<IEntityRepository>(
ENTITY_PROVIDERS.REPOSITORIES.ENTITY_REPOSITORY.provide,
);
});
it('should be defined', () => {
expect(controller).toBeDefined();
expect(controller['createUseCase']).toBeInstanceOf(CreateEntityUseCase);
});
describe('create', () => {
const arrange = CreateEntityFixture.arrangeForCreate();
test.each(arrange)('when body is $send_data', async ({ send_data, expected }) => {
const presenter = await controller.create(send_data);
const entity = await repository.findById(new EntityId(presenter.id));
expect(entity!.toJSON()).toStrictEqual({
id: presenter.id,
created_at: presenter.created_at,
...expected,
});
});
});
});
API Layer - E2E (test/)
O que testar:
- Fluxos HTTP completos
- Autenticação e autorização
- Validação de requests
- Contratos de API
- Persistência no banco
Como testar:
- Testes E2E (.e2e-spec.ts)
- startApp helper
- Extensão do supertest
- Test fixtures
- Validação completa de response
Exemplo:
describe('EntitiesController (e2e)', () => {
const appHelper = startApp();
let entityRepo: IEntityRepository;
beforeEach(async () => {
entityRepo = appHelper.app.get<IEntityRepository>(
ENTITY_PROVIDERS.REPOSITORIES.ENTITY_REPOSITORY.provide,
);
});
describe('/entities (POST)', () => {
describe('authentication', () => {
test('should return 401 when not authenticated', () => {
return request(appHelper.app.getHttpServer())
.post('/entities')
.send({})
.expect(401);
});
test('should return 403 when not admin', () => {
return request(appHelper.app.getHttpServer())
.post('/entities')
.authenticate(appHelper.app, false)
.send({})
.expect(403);
});
});
describe('validation errors', () => {
const invalidRequest = CreateEntityFixture.arrangeInvalidRequest();
const arrange = Object.keys(invalidRequest).map((key) => ({
label: key,
value: invalidRequest[key],
}));
test.each(arrange)('when body is $label', ({ value }) => {
return request(appHelper.app.getHttpServer())
.post('/entities')
.authenticate(appHelper.app)
.send(value.send_data)
.expect(422)
.expect(value.expected);
});
});
describe('successful creation', () => {
const arrange = CreateEntityFixture.arrangeForCreate();
test.each(arrange)('when body is $send_data', async ({ send_data, expected }) => {
const res = await request(appHelper.app.getHttpServer())
.post('/entities')
.authenticate(appHelper.app)
.send(send_data)
.expect(201);
expect(Object.keys(res.body)).toStrictEqual(['data']);
expect(Object.keys(res.body.data)).toStrictEqual(
CreateEntityFixture.keysInResponse,
);
const id = res.body.data.id;
const entity = await entityRepo.findById(new Uuid(id));
const presenter = EntitiesController.serialize(
EntityOutputMapper.toOutput(entity!),
);
const serialized = instanceToPlain(presenter);
expect(res.body.data).toStrictEqual({
id: serialized.id,
created_at: serialized.created_at,
...expected,
});
});
});
});
});
Checklist de Implementação
Ao criar ou refatorar testes, verifique:
Arquitetura
- Camadas separadas (domain, application, infra, nest-modules)
- Interfaces de repositório na camada de domínio
- Implementação in-memory para testes
- Zero dependências de framework no domain
Configuração
- Coverage threshold configurado (80% mínimo)
- Configurações separadas para unit e E2E
- Custom matchers implementados quando necessário
- SWC configurado como transformer
Padrões de Teste
- Fake Builder implementado para entidades principais
- Test fixtures criados para cenários E2E
- test.each usado para edge cases
- Helpers de setup/teardown implementados
Organização
- Testes ao lado do código (tests/)
- Nomenclatura correta (.spec.ts, .int-spec.ts, .e2e-spec.ts)
- Fixtures organizados por módulo
- Scripts NPM configurados (test, test:watch, test:cov, test:e2e)
Qualidade
- Mock de validação em testes de construção
- Spy em métodos importantes de repositório
- Validação completa de responses em E2E
- Testes independentes (sem vazamento de estado)
Métricas de Qualidade
Cobertura
- Mínimo obrigatório: 80% (statements, branches, functions, lines)
- Ideal para domain layer: >90%
Performance
- Unit tests: <5s para suite completa
- Integration tests: <30s
- E2E tests: <2min
Manutenibilidade
- Evitar testes duplicados
- Máximo 1 assertion complexa por test case
- Testes devem ser independentes
- Nomes descritivos e auto-explicativos
Scripts NPM
Configure sempre:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.config.ts",
"test:e2e:watch": "jest --config ./test/jest-e2e.config.ts --watch"
}
}
Dependências Necessárias
{
"devDependencies": {
"@nestjs/testing": "^10.0.0",
"@swc/jest": "^0.2.29",
"@types/jest": "^29.5.0",
"@types/supertest": "^2.0.12",
"chance": "^1.1.11",
"jest": "^29.5.0",
"sequelize": "^6.32.0",
"supertest": "^6.3.3"
}
}
Passo a Passo do Fluxo
- Analisar o código que precisa de testes
- Identificar a camada (domain, application, infra, nest-modules)
- Escolher o tipo de teste (unit, integration, e2e)
- Aplicar o padrão correto para aquela camada
- Implementar test fixtures/builders se necessário
- Configurar helpers de setup/teardown
- Escrever os testes seguindo os patterns
- Validar cobertura (mínimo 80%)
- Executar e verificar que todos passam
- Refatorar se necessário para melhorar clareza
Restrições e Limites
- NÃO misture estratégias de teste (use unit OU integration, não ambos no mesmo arquivo)
- NÃO coloque lógica complexa dentro de testes (mantenha setup simples)
- NÃO compartilhe estado entre testes (cada teste deve ser independente)
- NÃO teste detalhes de implementação (teste comportamento)
- NÃO ignore falhas de cobertura (investigue e corrija)
Exemplos de Uso
Exemplo 1: Criar testes para nova entidade
Entrada do usuário: "Preciso criar testes para a entidade Book que acabei de implementar"
Ações esperadas:
- Analisar estrutura da entidade Book
- Implementar BookFakeBuilder
- Criar testes unitários de domain (.spec.ts)
- Criar testes de use cases com repositório in-memory
- Configurar helpers se necessário
Exemplo 2: Refatorar testes existentes
Entrada do usuário: "Os testes do OrderService estão muito complexos e difíceis de manter"
Ações esperadas:
- Analisar testes atuais
- Identificar padrões que podem ser aplicados
- Implementar OrderFakeBuilder se não existe
- Criar fixtures reutilizáveis
- Aplicar test.each para cenários repetitivos
- Separar testes por responsabilidade
Exemplo 3: Configurar testes E2E para novo módulo
Entrada do usuário: "Preciso adicionar testes E2E para o módulo de Products"
Ações esperadas:
- Criar ProductFixture com cenários de teste
- Configurar autenticação se necessário
- Implementar testes de validação de request
- Implementar testes de sucesso
- Validar response completa e persistência no banco
- Verificar autenticação e autorização
Versão
Versão: 1.0.0 Data: 2025-01-18 Baseado em: Análise técnica do projeto Video Catalog Administration Backend
Changelog
1.0.0 (2025-01-18)
- Versão inicial da skill
- 8 padrões obrigatórios documentados
- Estratégias de teste por camada definidas
- Checklist de implementação completo
- Exemplos práticos para cada pattern