Node.js: Testes e Depuração

Última atualização: 2026-08-26

Bob escreveu uma API RESTful para uma plataforma de e-commerce e, toda vez que adicionava um novo recurso, algo quebrava. Testar manualmente com o Postman levava 30 minutos por implantação, e ele ainda deixava passar casos extremos. Ele introduziu o Jest para testes unitários e o Supertest para testes de integração de API — agora o npm test executa 50 testes em 3 segundos, e cada implantação é respaldada por uma verificação de CI automatizada. Ele também adicionou o winston para logging estruturado, de modo que, quando um bug escapa, os logs dizem exatamente o que deu errado.

Você aprenderá:


1. Visão geral dos testes

(1) Por que escrever testes

Sem testes Com testes
Verificação manual, 30 min a cada execução Automatizado, 3 segundos
Alterar uma coisa, quebrar tudo Alterar uma coisa, os testes indicam o que quebrou
Ansiedade antes da implantação Sinal verde, implantar com confiança
Medo de refatorar Refatorar com a rede de segurança dos testes

▶ Exemplo: (2) Pirâmide de Testes

100%
graph TD
    A["Unit Tests<br/>Many / Fast / Cheap"] --> B["Integration Tests<br/>Moderate / Slower"]
    B --> C["E2E Tests<br/>Few / Slow / Expensive"]
    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#F44336,color:#fff

(3) Comparação de ferramentas de teste do Node.js

Ferramenta Tipo Recursos Ideal para
Jest Unitário + Integração Configuração zero, mock/cobertura integrados, teste de snapshot Uso geral
Mocha Unitário + Integração Flexível, muitos plugins, requer chai/sinon Necessidades personalizadas
Vitest Unitário Ecossistema Vite, ESM nativo, muito rápido Projetos Vite
node:test Unitário Nativo do Node.js, sem dependências Projetos simples
Supertest Teste HTTP Simulação de requisições HTTP, teste de rotas Express Testes de integração de API
Playwright E2E Automação de navegador, suporte a vários navegadores E2E de front-end

Esta lição usa a combinação Jest + Supertest, a solução de testes mais difundida na comunidade Node.js.



2. Testes unitários com Jest

▶ Exemplo: (1) Instalação e configuração

BASH
npm install --save-dev jest

Adicione o script de teste ao package.json:

JSON
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

O Jest funciona sem configuração por padrão, localizando automaticamente os arquivos *.test.js ou *.spec.js.

▶ Exemplo: (2) Sintaxe básica: describe / it / expect

JAVASCRIPT
// math.js — o módulo a ser testado
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}

module.exports = { add, subtract, multiply, divide };
▶ Experimente
JAVASCRIPT
// math.test.js
const { add, subtract, multiply, divide } = require('./math');

describe('Math utilities', () => {
  test('add should sum two numbers', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });

  test('subtract should return the difference', () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test('multiply should return the product', () => {
    expect(multiply(3, 4)).toBe(12);
  });

  test('divide should throw on zero divisor', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
  });
});

Executar:

BASH
npm test

Output:

TEXT 📖 Somente leitura
 PASS  ./math.test.js
  Math utilities
    ✓ add should sum two numbers (2 ms)
    ✓ subtract should return the difference
    ✓ multiply should return the product
    ✓ divide should throw on zero divisor (1 ms)

Tests:       4 passed, 4 total
Time:        0.5s

(3) Matchers comuns

Matcher Significado Exemplo
toBe Igualdade estrita (===) expect(1 + 1).toBe(2)
toEqual Igualdade profunda expect(obj).toEqual({ a: 1 })
toBeTruthy / toBeFalsy Verificação booleana expect(flag).toBeTruthy()
toBeNull / toBeUndefined null / undefined expect(val).toBeNull()
toThrow Lança exceção expect(fn).toThrow()
toContain O array contém expect([1, 2, 3]).toContain(2)
toMatch Correspondência por regex expect('hello').toMatch(/ell/)
toHaveLength Verificação de comprimento expect('abc').toHaveLength(3)
resolves / rejects Resultado de Promise expect(promise).resolves.toBe(5)
toHaveBeenCalled Mock chamado expect(fn).toHaveBeenCalled()

▶ Exemplo: (4) Testes assíncronos

JAVASCRIPT
// async-functions.js
function fetchUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id, name: `User_${id}` });
    }, 100);
  });
}

async function getUserEmail(id) {
  const user = await fetchUser(id);
  return `${user.name.toLowerCase()}@example.com`;
}

module.exports = { fetchUser, getUserEmail };
▶ Experimente
JAVASCRIPT
// async-functions.test.js
const { fetchUser, getUserEmail } = require('./async-functions');

describe('Async function tests', () => {
  test('fetchUser returns user object', async () => {
    const user = await fetchUser(1);
    expect(user).toEqual({ id: 1, name: 'User_1' });
  });

  test('getUserEmail returns formatted email', async () => {
    const email = await getUserEmail(42);
    expect(email).toBe('user_42@example.com');
  });

  test('fetchUser resolves within 200ms', async () => {
    await expect(fetchUser(1)).resolves.toHaveProperty('id', 1);
  });
});

(5) Mocks (simulação)

Os mocks isolam dependências externas (bancos de dados, requisições HTTP, sistema de arquivos) para que os testes se concentrem apenas na lógica do módulo atual.

JAVASCRIPT
// email-service.js
class EmailService {
  send(to, subject, body) {
    // chamada SMTP real — lenta demais para testes unitários
    console.log(`Sending email to ${to}: ${subject}`);
    return true;
  }
}

class UserService {
  constructor(emailService) {
    this.emailService = emailService;
  }

  register(name, email) {
    // lógica de negócio: validar + enviar e-mail de boas-vindas
    if (!email.includes('@')) throw new Error('Invalid email');
    this.emailService.send(email, 'Welcome', `Hello ${name}!`);
    return { name, email, status: 'registered' };
  }
}

module.exports = { EmailService, UserService };
JAVASCRIPT
// email-service.test.js
const { EmailService, UserService } = require('./email-service');

describe('UserService', () => {
  let mockEmailService;
  let userService;

  beforeEach(() => {
    mockEmailService = {
      send: jest.fn().mockReturnValue(true),
    };
    userService = new UserService(mockEmailService);
  });

  test('register should call emailService.send', () => {
    const result = userService.register('Alice', 'alice@example.com');

    expect(result).toEqual({
      name: 'Alice',
      email: 'alice@example.com',
      status: 'registered',
    });

    expect(mockEmailService.send).toHaveBeenCalledWith(
      'alice@example.com',
      'Welcome',
      'Hello Alice!'
    );
  });

  test('register should throw on invalid email', () => {
    expect(() => userService.register('Bob', 'invalid')).toThrow('Invalid email');
    expect(mockEmailService.send).not.toHaveBeenCalled();
  });

  test('register should send exactly one email', () => {
    userService.register('Charlie', 'charlie@example.com');
    expect(mockEmailService.send).toHaveBeenCalledTimes(1);
  });
});

▶ Exemplo: Asserções básicas com Jest

JAVASCRIPT
test('basic matchers', () => {
  expect(2 + 2).toBe(4);
  expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
  expect([1, 2, 3]).toContain(2);
  expect('hello world').toMatch(/world/);
  expect(() => { throw new Error('fail'); }).toThrow('fail');
});
▶ Experimente

Mostra os matchers do Jest mais usados: toBe para primitivos, toEqual para objetos, toContain para arrays, toMatch para strings e toThrow para testes de erro.




3. Testes de integração de API com Supertest

▶ Exemplo: (1) Instalação e princípio

BASH
npm install --save-dev supertest

O Supertest inicia uma aplicação Express em memória, envia requisições HTTP e faz asserções sobre as respostas — sem a necessidade de escutar em uma porta real.

100%
flowchart LR
    A["Test File"] -->|"request(app)"| B["Express App<br/>(in-memory)"]
    B -->|"response"| A
    A -->|"assert"| C["Jest expect"]

▶ Exemplo: (2) Testando a API Express

JAVASCRIPT
// app.js — a aplicação Express (exportada sem listen)
const express = require('express');
const app = express();

app.use(express.json());

let products = [
  { id: 1, name: 'Laptop', price: 999.99 },
  { id: 2, name: 'Phone', price: 499.99 },
];

app.get('/api/products', (req, res) => {
  res.json(products);
});

app.get('/api/products/:id', (req, res) => {
  const product = products.find(p => p.id === parseInt(req.params.id));
  if (!product) return res.status(404).json({ error: 'Product not found' });
  res.json(product);
});

app.post('/api/products', (req, res) => {
  const { name, price } = req.body;
  if (!name || price == null) {
    return res.status(400).json({ error: 'Name and price are required' });
  }
  const newProduct = { id: products.length + 1, name, price };
  products.push(newProduct);
  res.status(201).json(newProduct);
});

module.exports = app;
▶ Experimente

Observação: o app.js apenas exporta o app; ele não chama app.listen() dentro do módulo. O listen fica no server.js, para que o Supertest possa testar o objeto app diretamente.

JAVASCRIPT
// app.test.js
const request = require('supertest');
const app = require('./app');

describe('Products API', () => {
  test('GET /api/products should return product list', async () => {
    const response = await request(app).get('/api/products');

    expect(response.status).toBe(200);
    expect(response.body).toHaveLength(2);
    expect(response.body[0]).toHaveProperty('name', 'Laptop');
  });

  test('GET /api/products/:id should return a single product', async () => {
    const response = await request(app).get('/api/products/1');

    expect(response.status).toBe(200);
    expect(response.body.id).toBe(1);
  });

  test('GET /api/products/:id should return 404 for missing product', async () => {
    const response = await request(app).get('/api/products/999');

    expect(response.status).toBe(404);
    expect(response.body).toHaveProperty('error', 'Product not found');
  });

  test('POST /api/products should create a new product', async () => {
    const response = await request(app)
      .post('/api/products')
      .send({ name: 'Tablet', price: 299.99 });

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('name', 'Tablet');
    expect(response.body).toHaveProperty('id', 3);
  });

  test('POST /api/products should return 400 for missing fields', async () => {
    const response = await request(app)
      .post('/api/products')
      .send({ name: 'Incomplete Product' });

    expect(response.status).toBe(400);
    expect(response.body).toHaveProperty('error');
  });
});

▶ Exemplo: (3) Testando API autenticada

JAVASCRIPT
// protected-routes.test.js
const request = require('supertest');
const app = require('./app');

describe('Protected API routes', () => {
  let token;

  beforeAll(async () => {
    // login para obter o token JWT
    const response = await request(app)
      .post('/api/auth/login')
      .send({ username: 'alice', password: 'secret123' });
    token = response.body.token;
  });

  test('should access protected route with valid token', async () => {
    const response = await request(app)
      .get('/api/profile')
      .set('Authorization', `Bearer ${token}`);

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('username', 'alice');
  });

  test('should reject access without token', async () => {
    const response = await request(app).get('/api/profile');

    expect(response.status).toBe(401);
  });

  test('should reject access with invalid token', async () => {
    const response = await request(app)
      .get('/api/profile')
      .set('Authorization', 'Bearer invalid_token_here');

    expect(response.status).toBe(401);
  });
});
▶ Experimente

▶ Exemplo: Teste de verificação de saúde da API

JAVASCRIPT
const request = require('supertest');
const express = require('express');

const app = express();
app.get('/health', (req, res) => res.json({ status: 'ok' }));

describe('GET /health', () => {
  it('returns 200 with status ok', async () => {
    const res = await request(app).get('/health');
    expect(res.status).toBe(200);
    expect(res.body.status).toBe('ok');
  });
});
▶ Experimente

Um teste mínimo de verificação de saúde da API usando o Supertest — cria uma aplicação Express básica, envia uma requisição GET e verifica o corpo da resposta JSON e o código de status.




4. Depuração no Node.js

(1) node inspect + Chrome DevTools

O Node.js tem um depurador embutido — sem necessidade de ferramentas extras.

BASH
node inspect app.js

Em seguida, abra chrome://inspect no Chrome e clique em "Open dedicated DevTools for Node".

Operações comuns de depuração:

Ação Atalho do DevTools Linha de comando
Continuar F8 c
Passar por cima F10 n
Entrar F11 s
Sair Shift+F11 o
Definir breakpoint Clicar no número da linha setBreakpoint()

▶ Exemplo: (2) Definindo breakpoints no código

JAVASCRIPT
// debug-demo.js
function calculateDiscount(price, memberLevel) {
  debugger; // a execução pausa aqui no inspetor
  let discount = 0;
  if (memberLevel === 'gold') discount = 0.2;
  else if (memberLevel === 'silver') discount = 0.1;
  const finalPrice = price * (1 - discount);
  return finalPrice;
}

console.log(calculateDiscount(100, 'gold'));
console.log(calculateDiscount(100, 'silver'));
console.log(calculateDiscount(100, 'unknown'));
▶ Experimente
BASH
node inspect debug-demo.js

▶ Exemplo: (3) Dicas de depuração com console

JAVASCRIPT
// métodos de depuração com console
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'admin' },
];

// Exibição em tabela
console.table(users);

// Medir o tempo de execução
console.time('database-query');
// ... lógica da consulta ...
console.timeEnd('database-query');

// Rastreamento de pilha
console.trace('Where is this called from?');

// Saída agrupada
console.group('User Details');
console.log('Name: Alice');
console.log('Role: admin');
console.groupEnd();
▶ Experimente

5. Logging estruturado com winston

(1) Por que usar winston em vez de console.log

console.log winston
Sem níveis de log Suporta debug/info/warn/error
Sem saída em arquivo Suporta Console + File + HTTP e outros transports
Sem formatação Suporta JSON / timestamp / formatos personalizados
Sem rotação de log Junte ao winston-daily-rotate-file para rotação automática
Não recomendado para produção Escolha de nível de produção

▶ Exemplo: (2) Instalação e configuração básica

BASH
npm install winston
npm install winston-daily-rotate-file
JAVASCRIPT
// logger.js
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'my-app' },
  transports: [
    // Logs de erro — arquivo separado
    new DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxSize: '20m',
      maxFiles: '14d',
    }),
    // Todos os logs — arquivo combinado
    new DailyRotateFile({
      filename: 'logs/combined-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
    }),
  ],
});

// Desenvolvimento: também registrar no console com saída colorida
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  }));
}

module.exports = logger;

▶ Exemplo: (3) Uso em uma aplicação Express

JAVASCRIPT
// app-with-logger.js
const express = require('express');
const logger = require('./logger');

const app = express();
app.use(express.json());

// Middleware de logging de requisições
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    logger.info('HTTP request', {
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration: `${duration}ms`,
    });
  });
  next();
});

app.get('/api/health', (req, res) => {
  logger.debug('Health check requested');
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

app.get('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  logger.info('User lookup', { userId });

  if (isNaN(userId)) {
    logger.warn('Invalid user ID format', { userId });
    return res.status(400).json({ error: 'Invalid user ID' });
  }

  // Simular busca de usuário
  const user = { id: parseInt(userId), name: 'Alice' };
  logger.info('User found', { userId: user.id, name: user.name });
  res.json(user);
});

app.use((err, req, res, next) => {
  logger.error('Unhandled error', { error: err.message, stack: err.stack });
  res.status(500).json({ error: 'Internal server error' });
});

module.exports = app;
▶ Experimente

Exemplo de saída do arquivo de log:

JSON
{"level":"info","message":"HTTP request","service":"my-app","timestamp":"2026-07-13 10:30:00","method":"GET","url":"/api/users/1","status":200,"duration":"15ms"}
{"level":"warn","message":"Invalid user ID format","service":"my-app","timestamp":"2026-07-13 10:30:05","userId":"abc"}
{"level":"error","message":"Unhandled error","service":"my-app","timestamp":"2026-07-13 10:31:00","error":"Cannot read property 'name' of undefined","stack":"TypeError: Cannot read property..."}

▶ Exemplo: Logging estruturado com winston

JAVASCRIPT
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

logger.info('Server started', { port: 3000, env: 'development' });
logger.warn('Memory usage high', { used: '850MB' });
logger.error('Database connection failed', { db: 'main', retry: 3 });
▶ Experimente

Demonstra o logging JSON estruturado com winston — diferentes níveis de log (info, warn, error) e metadados contextuais anexados a cada entrada de log.




6. Cobertura de testes e TDD

(1) Cobertura de testes

O Jest inclui o Istanbul para relatórios de cobertura:

BASH
npm run test:coverage

Output:

TEXT 📖 Somente leitura
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |    90.5 |    85.71 |     100 |    90.5 |
 math.js  |     100 |      100 |     100 |     100 |
 app.js   |   88.24 |    83.33 |     100 |   88.24 | 45-48
----------|---------|----------|---------|---------|-------------------

Dimensões de cobertura explicadas:

Dimensão Significado Meta sugerida
Statements Porcentagem de instruções executadas ≥ 80%
Branches Porcentagem de ramificações condicionais executadas ≥ 75%
Functions Porcentagem de funções chamadas ≥ 85%
Lines Porcentagem de linhas de código executadas ≥ 80%

Buscar 100% de cobertura tem retornos decrescentes; uma cobertura de 80% detecta a maioria dos bugs.

▶ Exemplo: (2) Fluxo de trabalho TDD (Red-Green-Refactor)

100%
flowchart LR
    A["🔴 Red<br/>Write failing test"] --> B["🟢 Green<br/>Write minimal code to pass"]
    B --> C["🔵 Refactor<br/>Improve code while tests pass"]
    C --> A
  1. Red: Escreva um teste primeiro (ele falhará porque o recurso ainda não existe)
  2. Green: Escreva o código mínimo para fazer o teste passar
  3. Refactor: Melhore o código enquanto os testes continuam passando

▶ Exemplo: Funções utilitárias de string com TDD

JAVASCRIPT
// string-utils.test.js — escreva o teste PRIMEIRO (Red)
const { capitalize, truncate, slugify } = require('./string-utils');

describe('StringUtils', () => {
  describe('capitalize', () => {
    test('should capitalize first letter', () => {
      expect(capitalize('hello')).toBe('Hello');
    });

    test('should handle empty string', () => {
      expect(capitalize('')).toBe('');
    });

    test('should handle already capitalized', () => {
      expect(capitalize('Hello')).toBe('Hello');
    });
  });

  describe('truncate', () => {
    test('should truncate long strings', () => {
      expect(truncate('Hello World', 5)).toBe('Hello...');
    });

    test('should not truncate short strings', () => {
      expect(truncate('Hi', 10)).toBe('Hi');
    });
  });

  describe('slugify', () => {
    test('should convert to URL slug', () => {
      expect(slugify('Hello World')).toBe('hello-world');
    });

    test('should handle special characters', () => {
      expect(slugify('C# & .NET!')).toBe('c-and-net');
    });
  });
});
▶ Experimente
JAVASCRIPT
// string-utils.js — escreva a implementação DEPOIS do teste (Green)
function capitalize(str) {
  if (!str) return '';
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function truncate(str, maxLength) {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength) + '...';
}

function slugify(str) {
  return str
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

module.exports = { capitalize, truncate, slugify };


7. Exemplo abrangente: escrevendo uma suíte de testes completa para uma API de gerenciamento de tarefas

▶ Exemplo: Suíte de testes da API de tarefas

Passo 1 — Código da aplicação

JAVASCRIPT 📖 Somente leitura
// tasks-app.js
const express = require('express');
const app = express();
app.use(express.json());

let tasks = [];
let nextId = 1;

function resetTasks() {
  tasks = [];
  nextId = 1;
}

app.get('/api/tasks', (req, res) => {
  const { status, priority } = req.query;
  let filtered = tasks;
  if (status) filtered = filtered.filter(t => t.status === status);
  if (priority) filtered = filtered.filter(t => t.priority === priority);
  res.json(filtered);
});

app.get('/api/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  res.json(task);
});

app.post('/api/tasks', (req, res) => {
  const { title, priority = 'medium' } = req.body;
  if (!title) return res.status(400).json({ error: 'Title is required' });
  const task = { id: nextId++, title, priority, status: 'pending' };
  tasks.push(task);
  res.status(201).json(task);
});

app.patch('/api/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  const { title, status, priority } = req.body;
  if (title) task.title = title;
  if (status) task.status = status;
  if (priority) task.priority = priority;
  res.json(task);
});

app.delete('/api/tasks/:id', (req, res) => {
  const index = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'Task not found' });
  const deleted = tasks.splice(index, 1);
  res.json(deleted[0]);
});

module.exports = { app, resetTasks };
44 linhas de lógica (limite de 40, somente leitura)

Passo 2 — Suíte de testes completa

JAVASCRIPT
// tasks-app.test.js
const request = require('supertest');
const { app, resetTasks } = require('./tasks-app');

beforeEach(() => {
  resetTasks();
});

describe('Tasks API — CRUD operations', () => {
  test('should create a task', async () => {
    const response = await request(app)
      .post('/api/tasks')
      .send({ title: 'Write tests', priority: 'high' });

    expect(response.status).toBe(201);
    expect(response.body).toMatchObject({
      id: 1,
      title: 'Write tests',
      priority: 'high',
      status: 'pending',
    });
  });

  test('should reject task without title', async () => {
    const response = await request(app)
      .post('/api/tasks')
      .send({ priority: 'high' });

    expect(response.status).toBe(400);
    expect(response.body.error).toBe('Title is required');
  });

  test('should list all tasks', async () => {
    await request(app).post('/api/tasks').send({ title: 'Task 1' });
    await request(app).post('/api/tasks').send({ title: 'Task 2' });

    const response = await request(app).get('/api/tasks');
    expect(response.status).toBe(200);
    expect(response.body).toHaveLength(2);
  });

  test('should filter tasks by status', async () => {
    await request(app).post('/api/tasks').send({ title: 'Pending task' });

    const createRes = await request(app).post('/api/tasks').send({ title: 'Done task' });
    await request(app).patch(`/api/tasks/${createRes.body.id}`).send({ status: 'done' });

    const response = await request(app).get('/api/tasks?status=done');
    expect(response.body).toHaveLength(1);
    expect(response.body[0].status).toBe('done');
  });

  test('should update a task', async () => {
    const createRes = await request(app).post('/api/tasks').send({ title: 'Old title' });

    const response = await request(app)
      .patch(`/api/tasks/${createRes.body.id}`)
      .send({ title: 'New title', status: 'done' });

    expect(response.status).toBe(200);
    expect(response.body.title).toBe('New title');
    expect(response.body.status).toBe('done');
  });

  test('should delete a task', async () => {
    const createRes = await request(app).post('/api/tasks').send({ title: 'To delete' });

    const response = await request(app).delete(`/api/tasks/${createRes.body.id}`);
    expect(response.status).toBe(200);

    const listRes = await request(app).get('/api/tasks');
    expect(listRes.body).toHaveLength(0);
  });

  test('should return 404 for non-existent task', async () => {
    const response = await request(app).get('/api/tasks/999');
    expect(response.status).toBe(404);
  });
});

Executar:

BASH
npm test

Output:

TEXT 📖 Somente leitura
 PASS  ./tasks-app.test.js
  Tasks API — CRUD operations
    ✓ should create a task (15 ms)
    ✓ should reject task without title (3 ms)
    ✓ should list all tasks (4 ms)
    ✓ should filter tasks by status (8 ms)
    ✓ should update a task (5 ms)
    ✓ should delete a task (5 ms)
    ✓ should return 404 for non-existent task (2 ms)

Tests:       7 passed, 7 total
Time:        0.8s

❓ Perguntas Frequentes

P: Devo escolher Jest ou Mocha? R: O Jest tem configuração zero com mock/cobertura integrados, ótimo para a maioria dos projetos; o Mocha é flexível, mas precisa de chai/sinon/istanbul, sendo ideal para projetos que exigem personalização profunda. O Jest é recomendado para novos projetos.

P: Qual é a diferença entre testes unitários e testes de integração? R: Os testes unitários isolam e testam funções/módulos individuais usando mocks para substituir dependências externas — são rápidos. Os testes de integração verificam a cooperação entre vários módulos (ex.: API + banco de dados) usando componentes reais — são mais lentos, mas mais próximos dos cenários reais.

P: Os testes precisam de uma conexão real com o banco de dados? R: Os testes unitários nunca devem usar um banco de dados real — use mocks. Os testes de integração podem usar um banco de dados em memória (ex.: SQLite :memory:) ou uma instância de banco de dados de teste dedicada. Os testes E2E usam um banco de dados do ambiente de teste.

P: Quais são os níveis de log do winston? R: Do menor para o maior: error → warn → info → http → verbose → debug → silly. Quando o nível é definido como info, apenas info e acima são emitidos.

P: Como depurar os próprios testes do Jest? R: Execute node --inspect-brk node_modules/.bin/jest --runInBand e depure no Chrome DevTools. O --runInBand garante que o Jest execute em uma única thread para que os breakpoints funcionem.

P: O Supertest precisa iniciar um servidor real? R: Não. O Supertest cria um servidor em memória via http.createServer(app) — nenhuma porta é ocupada e ele fecha automaticamente quando os testes terminam.


📖 Resumo


📝 Exercícios

  1. Escreva testes unitários com Jest para a seguinte função: function isPalindrome(str) { return str === str.split('').reverse().join(''); }, cobrindo palíndromos normais, não palíndromos, strings vazias e cenários com letras maiúsculas e minúsculas.
  2. Crie uma API Express (CRUD para /api/books) e escreva pelo menos 5 testes de integração usando o Supertest (criar, ler, atualizar, excluir, 404).
  3. Adicione um middleware de logging com winston a uma aplicação Express existente, registrando método/url/status/duração para cada requisição, com rotação diária de logs.
  4. Use TDD para desenvolver uma função formatCurrency(amount, currency): primeiro escreva um teste que falhe (ex.: formatCurrency(1234.5, 'USD')'$1,234.50') e depois escreva a implementação.
  5. Execute jest --coverage, encontre arquivos com cobertura abaixo de 80% e adicione casos de teste para atingir a meta de cobertura.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%