Identificação
- Módulo: 5 — Node.js, NestJS e APIs
- Duração: 2 horas
- Tipo: teoria aplicada e laboratório
- Entrega: observação de requisições e suíte essencial de testes
- Laboratório:
../exemplos/aula-5.7/index.html
Introdução
Quando uma API falha, dizer “não funcionou” não é suficiente. Precisamos saber qual operação ocorreu, quanto tempo levou, qual status foi devolvido e como localizar os registros relacionados sem expor dados privados.
Também não é seguro depender apenas de testes manuais. Uma alteração pequena pode quebrar uma regra já concluída. Testes automatizados executam cenários repetíveis e avisam quando o comportamento observado diverge do esperado.
Um registro é uma evidência produzida durante a execução. Um interceptador
(interceptor) envolve o processamento de uma operação antes e depois do controlador.
Um teste prepara uma situação, executa o código e verifica o resultado.
Nesta aula responderemos:
console.logé suficiente em uma API?- registro estruturado é apenas JSON?
- interceptador substitui filtro de exceção?
- podemos registrar o corpo inteiro da requisição?
- teste unitário precisa iniciar servidor?
- teste de integração precisa usar MySQL?
- cobertura alta garante qualidade?
Objetivos
Ao concluir a aula, você será capaz de:
- diferenciar registros, métricas e rastreamento distribuído;
- usar o
Loggerdo NestJS com contexto; - produzir registros estruturados e seguros;
- correlacionar eventos da mesma requisição;
- medir duração com um interceptador;
- diferenciar middleware, guarda, pipe, interceptador e filtro;
- testar serviços isoladamente;
- testar controladores com dependências substituídas;
- testar endpoints com uma aplicação NestJS real em memória;
- escolher casos normais, limites e falhas importantes.
Pré-requisitos
- aulas 5.1 a 5.6 concluídas;
- injeção de dependência;
- exceções e filtros;
- noção de
Promiseeasync/await; - familiaridade com status HTTP.
1. Observabilidade em linguagem simples
Observabilidade é a capacidade de compreender o estado interno de um sistema a partir de sinais produzidos por ele.
| Sinal | Responde principalmente |
|---|---|
| registros | o que aconteceu em um evento? |
| métricas | quantas vezes e com qual tendência? |
| rastreamento | por onde uma operação passou? |
Nesta aula focaremos registros e correlação. Métricas e rastreamento distribuído serão aprofundados na etapa de produção.
2. Logger do NestJS
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
create(input: CreateTaskDto): Task {
const task = this.repository.add(input);
this.logger.log(`Tarefa criada: ${task.id}`);
return task;
}
}
O contexto TasksService ajuda a localizar a origem do evento.
3. Níveis de registro
| Nível | Uso típico |
|---|---|
fatal |
processo não pode continuar |
error |
operação falhou e exige investigação |
warn |
situação anormal, mas controlada |
log |
evento normal relevante |
debug |
detalhe útil durante diagnóstico |
verbose |
detalhe muito fino e normalmente temporário |
Não transforme todo evento em erro. Níveis incorretos criam alarmes inúteis.
4. Configuração por ambiente
const app = await NestFactory.create(AppModule, {
logger: process.env.NODE_ENV === 'production'
? ['fatal', 'error', 'warn', 'log']
: ['fatal', 'error', 'warn', 'log', 'debug'],
});
Desabilitar todo registro em produção remove evidências importantes. Prefira controlar níveis e conteúdo.
5. Registro estruturado
Texto livre é difícil de pesquisar. Uma estrutura estável facilita filtros:
{
"event": "http.request.completed",
"method": "POST",
"path": "/api/tasks",
"statusCode": 201,
"durationMs": 18,
"correlationId": "req-a81f"
}
No ConsoleLogger, a opção json: true produz registros do sistema em JSON. Eventos da
aplicação também precisam manter campos coerentes.
6. O que não registrar
Não registre:
- senhas e confirmações de senha;
- tokens de acesso ou atualização;
- chaves de API;
- cabeçalho
Authorizationcompleto; - conteúdo privado de documentos;
- dados pessoais sem necessidade e base legítima;
- variáveis de ambiente completas;
- corpo integral de toda requisição.
Registrar dados demais é risco de segurança, privacidade e custo.
7. Correlação
Um identificador de correlação conecta eventos da mesma operação:
requisição recebida correlationId=req-a81f
serviço executado correlationId=req-a81f
resposta enviada correlationId=req-a81f
O identificador não é senha, mas ainda deve ser validado e ter tamanho limitado.
8. Middleware de correlação
export function correlationMiddleware(
request: Request,
response: Response,
next: NextFunction,
): void {
const incoming = request.header('X-Correlation-ID');
const correlationId = isSafeCorrelationId(incoming)
? incoming
: randomUUID();
response.setHeader('X-Correlation-ID', correlationId);
request.correlationId = correlationId;
next();
}
O middleware prepara contexto antes de o NestJS selecionar a operação.
9. O que é um interceptador?
O interceptador recebe um ExecutionContext e um CallHandler:
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<unknown> {
return next.handle();
}
next.handle() representa a continuação do processamento. Sem chamá-lo, o controlador
não executa, a menos que o interceptador produza uma resposta conscientemente.
10. Interceptador de duração
@Injectable()
export class HttpLoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(HttpLoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const startedAt = performance.now();
const http = context.switchToHttp();
const request = http.getRequest<RequestWithCorrelation>();
const response = http.getResponse<Response>();
return next.handle().pipe(
finalize(() => {
this.logger.log(JSON.stringify({
event: 'http.request.completed',
method: request.method,
path: request.route?.path ?? request.path,
statusCode: response.statusCode,
durationMs: Math.round(performance.now() - startedAt),
correlationId: request.correlationId,
}));
}),
);
}
}
Use o modelo de rota quando possível para evitar registrar identificadores pessoais no
caminho, como /users/83921.
11. Registro global
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: HttpLoggingInterceptor,
},
],
})
export class AppModule {}
Registrar pelo contêiner permite injetar dependências no interceptador.
12. Interceptador e filtro de exceção
| Recurso | Papel principal |
|---|---|
| interceptador | envolver execução, medir, transformar |
| filtro | converter exceção que escapou em resposta |
O interceptador pode observar a finalização. O filtro continua responsável pelo contrato seguro de erro.
13. Duração não é desempenho completo
Medir a duração ajuda, mas não explica sozinho a causa. Uma operação lenta pode depender de rede, banco, CPU, fila ou serviço externo. Não conclua sem evidência adicional.
14. O que é um teste automatizado?
Um teste possui três movimentos:
- preparar dados e dependências;
- executar uma unidade ou operação;
- verificar resultado e efeitos observáveis.
O padrão é conhecido como preparar, agir e verificar (Arrange, Act, Assert).
15. Pirâmide de testes da API
poucos testes ponta a ponta
alguns testes de integração HTTP
muitos testes de serviços e regras
Testes menores são rápidos e localizam falhas. Testes HTTP verificam a composição real.
16. Ambiente de testes do NestJS
O pacote @nestjs/testing cria um módulo de teste:
const moduleRef = await Test.createTestingModule({
providers: [TasksService, InMemoryTasksRepository],
}).compile();
const service = moduleRef.get(TasksService);
Isso usa o contêiner de injeção sem abrir uma porta HTTP.
17. Teste unitário de serviço
describe('TasksService', () => {
it('cria uma tarefa aberta', () => {
const repository = new InMemoryTasksRepository();
const service = new TasksService(repository);
const task = service.create({ title: 'Testar a API' });
expect(task).toMatchObject({
title: 'Testar a API',
completed: false,
});
});
});
Esse teste exercita regra e repositório em memória. Não precisa de MySQL.
18. Substituição de dependência
const repository = {
findById: jest.fn().mockReturnValue(undefined),
};
const service = new TasksService(repository);
expect(() => service.findOne('task-404'))
.toThrow(NotFoundException);
Uma substituição controlada permite produzir situações difíceis sem depender de rede.
19. Teste de controlador
const tasksService = {
list: jest.fn().mockReturnValue([]),
};
const moduleRef = await Test.createTestingModule({
controllers: [TasksController],
providers: [{ provide: TasksService, useValue: tasksService }],
}).compile();
const controller = moduleRef.get(TasksController);
expect(controller.list()).toEqual([]);
expect(tasksService.list).toHaveBeenCalledTimes(1);
O teste verifica tradução e delegação, não repete regras do serviço.
20. Teste HTTP de integração
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const app = moduleRef.createNestApplication();
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
await request(app.getHttpServer())
.post('/api/tasks')
.send({ title: 'Testar contrato HTTP' })
.expect(201)
.expect(({ body }) => {
expect(body.title).toBe('Testar contrato HTTP');
});
await app.close();
O servidor é criado em memória para o teste; não é necessário escolher uma porta.
21. Reproduza a configuração real
Se produção usa prefixo, pipes, filtros e interceptadores globais, o teste HTTP deve aplicar a mesma configuração. Extraia uma função compartilhada:
export function configureApplication(app: INestApplication): void {
app.setGlobalPrefix('api');
app.useGlobalPipes(createValidationPipe());
}
Isso reduz diferenças entre teste e execução real.
22. Casos mínimos por endpoint
Para POST /api/tasks:
- entrada válida retorna
201; - título curto retorna
400; - campo desconhecido segue a política escolhida;
- duplicidade retorna
409quando aplicável; - segredo nunca aparece em resposta ou registro.
23. Testes devem ser independentes
Cada teste precisa preparar seu próprio estado. Não dependa da ordem:
beforeEach(() => {
repository.clear();
});
Um teste que passa sozinho e falha no conjunto indica estado compartilhado indevido.
24. Cobertura
Cobertura mostra quais linhas ou ramos foram executados. Ela não confirma:
- qualidade das verificações;
- casos de negócio relevantes;
- ausência de vulnerabilidade;
- coerência da documentação;
- comportamento de dependências reais.
Use cobertura como mapa, não como certificado.
25. Teste não deve registrar segredo
Dados de teste também podem aparecer em relatórios e sistemas de integração contínua. Use valores fictícios e verifique a ocultação:
expect(serializedEvent).not.toContain('DATABASE_PASSWORD');
expect(serializedEvent).not.toContain('Bearer token-real');
26. Roteiro de implementação
- escolher eventos realmente úteis;
- definir campos estáveis para registros;
- validar ou gerar correlação;
- criar interceptador de duração;
- ocultar dados sensíveis;
- testar serviço e falhas de regra;
- testar controlador com dependência substituída;
- criar testes HTTP de sucesso e validação;
- fechar a aplicação após cada conjunto;
- executar a suíte sem depender de ordem.
27. Laboratório guiado
Abra o laboratório de observação e testes.
Etapa 1 — Envie uma operação
Escolha sucesso, validação, ausência ou falha inesperada.
Etapa 2 — Acompanhe o ciclo
Observe correlação, duração, status e registro público seguro.
Etapa 3 — Execute a suíte
Ative casos e confira quais camadas cada teste cobre.
Etapa 4 — Introduza uma regressão
Altere o comportamento simulado e veja qual teste detecta a falha.
28. Erros comuns
Registrar tudo
Volume excessivo aumenta custo e risco sem melhorar o diagnóstico.
Registrar senha antes de ocultar
O vazamento já ocorreu no momento da escrita.
Medir somente sucesso
Falhas também precisam de status, duração e correlação.
Testar método privado
Prefira comportamento observável. Métodos privados podem mudar sem alterar o contrato.
Repetir implementação no teste
O teste pode reproduzir o mesmo erro e passar. Verifique entradas e saídas.
Usar o banco real em todo teste
Isso torna a suíte lenta e frágil. Use diferentes níveis conscientemente.
29. Exercícios
- crie um evento estruturado para
GET /api/tasks; - remova identificadores sensíveis de um caminho registrado;
- teste criação com título válido;
- teste falha para título curto;
- teste controlador com serviço substituído;
- crie teste HTTP para
404; - explique por que cobertura de 100% pode esconder defeitos.
30. Desafio individual
Implemente observação segura de PATCH /api/tasks/:id:
- correlação;
- duração;
- status final;
- caminho normalizado;
- nenhuma entrada sensível;
- teste de sucesso;
- teste de recurso ausente;
- teste de validação.
31. Lista de verificação de conclusão
- Diferencio registro, métrica e rastreamento.
- Uso contexto no
Logger. - Conheço os níveis de registro.
- Não registro credenciais ou corpos indiscriminadamente.
- Correlaciono eventos da mesma operação.
- Sei o papel de um interceptador.
- Testo regras sem abrir servidor.
- Substituo dependências conscientemente.
- Testo endpoints com configuração próxima da real.
- Minha suíte não depende de ordem.
32. Rubrica da entrega
| Critério | Pontos |
|---|---|
| Registros estruturados | 20 |
| Segurança e correlação | 20 |
| Interceptador e duração | 20 |
| Testes unitários | 20 |
| Testes HTTP e independência | 20 |
| Total | 100 |
33. Resumo
- registros explicam eventos específicos;
- correlação conecta evidências da mesma operação;
- interceptadores envolvem execução e podem medir duração;
- filtros continuam responsáveis por respostas de exceção;
- testes pequenos verificam regras rapidamente;
- testes HTTP confirmam a composição da aplicação;
- cobertura orienta investigação, mas não garante qualidade.
Próxima aula
Na Aula 5.8, compilaremos a API, reuniremos evidências, documentaremos a execução local e concluiremos o projeto do Módulo 5.