Testes e Debugging
Aula 5 de 7
Testing com Jest e Vitest
npm install -D jest
npm install -D vitest
// vitest.config.js
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov']
}
}
});
Testes Unitários
// src/math.js
function sum(a, b) { return a + b; }
function divide(a, b) {
if (b === 0) throw new Error('Divisão por zero');
return a / b;
}
module.exports = { sum, divide };
// tests/math.test.js
const { sum, divide } = require('../src/math');
describe('Math', () => {
describe('sum', () => {
it('deve somar dois números positivos', () => {
expect(sum(2, 3)).toBe(5);
});
it('deve somar números negativos', () => {
expect(sum(-1, -2)).toBe(-3);
});
});
describe('divide', () => {
it('deve dividir dois números', () => {
expect(divide(10, 2)).toBe(5);
});
it('deve lançar erro ao dividir por zero', () => {
expect(() => divide(5, 0)).toThrow('Divisão por zero');
});
});
});
Testes de API com Supertest
npm install -D supertest
// src/app.js
const express = require('express');
const app = express();
app.use(express.json());
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Nome e email são obrigatórios' });
}
res.status(201).json({ id: 1, name, email });
});
module.exports = app;
// tests/api.test.js
const request = require('supertest');
const app = require('../src/app');
describe('API', () => {
it('GET /health deve retornar status ok', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.body.status).toBe('ok');
});
it('POST /users deve criar usuário', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Alice', email: '[email protected]' });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe('Alice');
});
it('POST /users deve validar campos obrigatórios', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Alice' });
expect(res.status).toBe(400);
expect(res.body.error).toBeDefined();
});
});
Mocking
// src/users.js
const prisma = require('./prisma');
async function getUsers() {
return prisma.user.findMany({
where: { active: true },
select: { id: true, name: true, email: true }
});
}
module.exports = { getUsers };
// tests/users.test.js
const { getUsers } = require('../src/users');
const prisma = require('../src/prisma');
jest.mock('../src/prisma', () => ({
user: {
findMany: jest.fn()
}
}));
describe('Users Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('deve retornar usuários ativos', async () => {
const mockUsers = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' }
];
prisma.user.findMany.mockResolvedValue(mockUsers);
const result = await getUsers();
expect(result).toEqual(mockUsers);
expect(prisma.user.findMany).toHaveBeenCalledWith({
where: { active: true },
select: { id: true, name: true, email: true }
});
});
it('deve retornar array vazio se não houver usuários', async () => {
prisma.user.findMany.mockResolvedValue([]);
const result = await getUsers();
expect(result).toEqual([]);
});
});
Sinon (stubs/spies)
const sinon = require('sinon');
describe('Com Sinon', () => {
it('deve spy em uma função', () => {
const callback = sinon.spy();
callback('argumento');
expect(callback.calledWith('argumento')).toBe(true);
});
it('deve stub uma função', () => {
const stub = sinon.stub().returns(42);
expect(stub()).toBe(42);
});
});
Debugging
Node.js --inspect
# Modo debug
node --inspect src/server.js
node --inspect-brk src/server.js # pausa na primeira linha
# Debug com Chrome DevTools
# Abra chrome://inspect no Chrome
ndb (alternative)
npm install -g ndb
ndb src/server.js
VS Code (launch.json)
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Server",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/server.js"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229
}
]
}
Logging Estruturado
Pino (rápido)
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
});
logger.info({ userId: 123 }, 'Usuário logado');
logger.error({ err: new Error('Falha na conexão') }, 'Erro no banco');
logger.debug('Mensagem de debug');
// Contexto child
const requestLogger = logger.child({ requestId: 'abc-123' });
requestLogger.info('Processando requisição');
Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
logger.info({ message: 'Servidor iniciado', port: 3000 });
Lab: Exercício - Suite de Testes Completa
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const prisma = require('../../src/prisma');
// Setup e teardown
beforeAll(async () => {
await prisma.$connect();
});
afterAll(async () => {
await prisma.$disconnect();
});
beforeEach(async () => {
await prisma.user.deleteMany();
});
describe('Users API', () => {
it('deve listar usuários vazia', async () => {
const res = await request(app).get('/users');
expect(res.status).toBe(200);
expect(res.body).toEqual([]);
});
it('deve criar e listar usuários', async () => {
await request(app)
.post('/users')
.send({ name: 'Alice', email: '[email protected]', password: '123' });
const res = await request(app).get('/users');
expect(res.status).toBe(200);
expect(res.body).toHaveLength(1);
expect(res.body[0].name).toBe('Alice');
});
it('deve retornar 404 para usuário inexistente', async () => {
const res = await request(app).get('/users/999');
expect(res.status).toBe(404);
});
});
# Rodar testes
npm test
npm run test:coverage
npm run test:watch
# Modo debug
node --inspect src/server.js
Use Vitest para projetos modernos (mais rápido, compatível com Jest API). Sempre faça mock de dependências externas (banco, API). Supertest permite testar endpoints Express sem precisar subir o servidor. Pino é 5x mais rápido que Winston em produção.