Introdução: Por que testes avançados são um must-have para projetos JavaScript
No desenvolvimento moderno com Node.js, os testes deixaram de ser uma etapa opcional. Sem uma cobertura de código de qualidade com testes, qualquer refatoração ou adição de nova funcionalidade corre o risco de se tornar uma "roleta russa". No entanto, simplesmente escrever alguns asserts não é suficiente. Em um nível avançado, o desenvolvedor deve dominar três camadas principais: Testes Unitários (verificação de módulos isolados), Testes de Integração (interação entre componentes, BD, API) e Testes E2E (simulação de ações do usuário). Neste artigo, abordaremos técnicas práticas para cada camada usando Node.js com ferramentas modernas: Jest, Supertest e Playwright.
1. Testes Unitários: isolamento, mocks e stubs em nível avançado
Testes unitários verificam o funcionamento de uma função ou módulo isoladamente de dependências externas (BD, sistema de arquivos, requisições de rede). A tarefa principal é mockar corretamente essas dependências para que o teste seja rápido e determinístico.
Técnicas avançadas de mocking no Jest
Vamos considerar um exemplo: uma função que acessa uma API externa e armazena o resultado em cache no Redis. Precisamos testar a lógica de cache sem realizar requisições reais.
// cacheService.jsconst axios = require('axios');const redis = require('redis');
class CacheService { constructor() { this.client = redis.createClient(); }
async fetchData(url) { const cached = await this.client.get(url); if (cached) return JSON.parse(cached);
const response = await axios.get(url); await this.client.setEx(url, 3600, JSON.stringify(response.data)); return response.data; }}
module.exports = CacheService;// cacheService.test.jsconst CacheService = require('./cacheService');const axios = require('axios');const redis = require('redis');
jest.mock('axios');jest.mock('redis', () => ({ createClient: jest.fn(() => ({ get: jest.fn(), setEx: jest.fn() }))}));
describe('CacheService', () => { let service; let mockRedisClient;
beforeEach(() => { mockRedisClient = redis.createClient(); service = new CacheService(); });
test('should return cached data if present', async () => { const url = 'https://api.example.com/data'; const cachedData = { id: 1, value: 'cached' }; mockRedisClient.get.mockResolvedValue(JSON.stringify(cachedData));
const result = await service.fetchData(url);
expect(result).toEqual(cachedData); expect(axios.get).not.toHaveBeenCalled(); });
test('should fetch and cache data if not cached', async () => { const url = 'https://api.example.com/data'; const apiData = { id: 2, value: 'fresh' }; mockRedisClient.get.mockResolvedValue(null); axios.get.mockResolvedValue({ data: apiData });
const result = await service.fetchData(url);
expect(result).toEqual(apiData); expect(mockRedisClient.setEx).toHaveBeenCalledWith(url, 3600, JSON.stringify(apiData)); });});Pontos-chave:
jest.mock('axios')mocka automaticamente todo o módulo axios.- Para o Redis, usamos uma fábrica que retorna um mock com os métodos
getesetEx. - Verificamos que, quando o cache existe, a chamada externa não ocorre.
2. Testes de Integração: testando a interação com BD real e API
Testes de integração verificam como diferentes partes do sistema funcionam juntas. Para Node.js, isso geralmente significa testar rotas do Express com um banco de dados real (ou de teste). A melhor ferramenta é o Supertest em conjunto com o Jest.
Configuração do BD de teste e Supertest
Exemplo: API para gerenciamento de usuários. Usamos SQLite em memória para isolação