Padroes de Teste
Padrao de testes unitarios para o backend do Facter Truck usando Jest.
Estrutura de Arquivos
src/application/{domain}/__tests__/
├── factories/
│ └── make-{entity}.factory.ts # Factories para criar entidades de teste
├── mocks/
│ └── {repository}.mock.ts # Mocks de repositories
└── {use-case}.spec.ts # Arquivos de teste
Mock Pattern
Usamos jest.fn() para mocks de repositories. NAO usamos InMemory repositories.
// __tests__/mocks/work-order-repository.mock.ts
import { WorkOrderRepository } from '@/core/domain/repositories/work-order.repository';
export function createMockWorkOrderRepository() {
return {
create: jest.fn(),
findById: jest.fn(),
findMany: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
} as jest.Mocked<Record<keyof WorkOrderRepository, jest.Mock>>;
}
Cada metodo do repository abstrato recebe um jest.fn(). Isso permite:
- Verificar se o metodo foi chamado:
expect(repo.create).toHaveBeenCalledWith(...) - Configurar retornos:
repo.findById.mockResolvedValue(entity) - Verificar que metodos NAO foram chamados em cenarios de erro
Factory Pattern
Factories criam entidades validas para testes com valores default sobreescriveis:
// __tests__/factories/make-work-order.factory.ts
import { WorkOrder } from '@/core/domain/entities/work-order';
export function makeWorkOrder(overrides: Partial<WorkOrder> = {}): WorkOrder {
return new WorkOrder({
companyId: 'company-1',
vehicleId: 'vehicle-1',
status: MaintenanceStatus.Fila,
priority: 'MEDIA',
...overrides,
});
}
Test Pattern (AAA)
Testes seguem Arrange-Act-Assert com convencoes:
sutpara subject under test (o use case)beforeEachrecria mocks a cada teste- Blocos separados:
describe('success scenarios')+describe('error scenarios')
Exemplo completo
describe('StartMaintenanceUseCase', () => {
let sut: StartMaintenanceUseCase;
let workOrderRepository: ReturnType<typeof createMockWorkOrderRepository>;
let eventEmitter: { emit: jest.Mock };
beforeEach(() => {
workOrderRepository = createMockWorkOrderRepository();
eventEmitter = { emit: jest.fn() };
sut = new StartMaintenanceUseCase(
workOrderRepository as any,
eventEmitter as any,
);
});
describe('success scenarios', () => {
it('should start maintenance for a work order in queue', async () => {
// Arrange
const workOrder = makeWorkOrder({ status: MaintenanceStatus.Fila });
workOrderRepository.findById.mockResolvedValue(workOrder);
// Act
const result = await sut.execute({
companyId: 'company-1',
workOrderId: workOrder.id,
userId: 'user-1',
});
// Assert
expect(result.status).toBe(MaintenanceStatus.Manutencao);
expect(workOrderRepository.update).toHaveBeenCalledWith(
expect.objectContaining({ status: MaintenanceStatus.Manutencao }),
);
expect(eventEmitter.emit).toHaveBeenCalledWith(
DomainEvents.WORK_ORDER_MAINTENANCE_STARTED,
expect.any(Object),
);
});
});
describe('error scenarios', () => {
it('should throw WorkOrderNotFoundError when work order does not exist', async () => {
workOrderRepository.findById.mockResolvedValue(null);
await expect(
sut.execute({
companyId: 'company-1',
workOrderId: 'non-existent',
userId: 'user-1',
}),
).rejects.toThrow(WorkOrderNotFoundError);
// Verify side effects did NOT happen
expect(workOrderRepository.update).not.toHaveBeenCalled();
expect(eventEmitter.emit).not.toHaveBeenCalled();
});
it('should throw InvalidStatusTransitionError when not in queue', async () => {
const workOrder = makeWorkOrder({ status: MaintenanceStatus.Finalizada });
workOrderRepository.findById.mockResolvedValue(workOrder);
await expect(
sut.execute({
companyId: 'company-1',
workOrderId: workOrder.id,
userId: 'user-1',
}),
).rejects.toThrow(InvalidStatusTransitionError);
expect(workOrderRepository.update).not.toHaveBeenCalled();
});
});
});
Regras de Teste
- 100% de branches no use case. Todo
if, todothrow, todo cenario alternativo. - Cenarios de sucesso E erro. Ambos obrigatorios.
- Erros: usar
.rejects.toThrow(SpecificDomainError)+ verificar querepo.saveNAO foi chamado. - Sem mocks globais. Cada
beforeEachrecria mocks limpos. - Testes isolados. Nenhum teste depende de outro.
- Nomes descritivos:
should throw X when Y,should create Z with correct data.
Checklist de Code Review
- Todos os branches do use case estao cobertos?
- Cenarios de erro verificam que side effects NAO ocorreram?
- Usa
sutpara o subject under test? - Usa factories em vez de criar objetos inline?
- Mocks sao recriados no
beforeEach? - Erros de dominio especificos sao verificados (nao
Errorgenerico)?
Comandos
pnpm test # Rodar todos os testes
pnpm test:watch # Modo watch
pnpm test:cov # Com coverage
pnpm test -- --testPathPattern=work-order # Dominio especifico
Referencias
- Clean Architecture -- Estrutura de use cases
- Domain Errors -- Erros usados nos testes