Introdução: Por que testar em TypeScript é um must have
TypeScript — uma ferramenta poderosa que adiciona tipagem estática ao JavaScript. No entanto, mesmo o sistema de tipos mais rigoroso não garante que seu código funcionará corretamente em tempo de execução. Bugs, erros lógicos, tratamento incorreto de casos extremos — tudo isso fica fora do escopo da compilação. É aqui que os testes entram em cena.
Neste artigo, vamos abordar três níveis principais de teste em TypeScript: unit (unitário), integration (integração) e e2e (ponta a ponta). Você aprenderá como eles diferem, quando aplicar cada um e verá exemplos reais de código. Após a leitura, você será capaz de configurar um pipeline de testes completo para seu projeto TypeScript.
1. Testes unitários: verificando unidades atômicas de código
Testes unitários são o nível de verificação mais rápido e barato. Eles isolam uma única função, método ou classe e a testam separadamente das dependências externas (bancos de dados, APIs, sistema de arquivos).
1.1 Escolha da ferramenta e configuração
O framework mais popular para TypeScript é o Jest. Ele possui suporte nativo a TypeScript através de ts-jest ou @swc/jest.
// Instalaçãonpm install --save-dev jest @types/jest ts-jest
// Inicialização da configuraçãonpx ts-jest config:initApós isso, o arquivo jest.config.js com as configurações aparecerá na raiz.
1.2 Exemplo de teste unitário para uma função
Suponha que temos uma função simples para calcular desconto:
// discount.tsexport function applyDiscount(price: number, discountPercent: number): number { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Porcentagem de desconto inválida'); } return price - (price * discountPercent) / 100;}Vamos escrever um teste unitário para ela:
// discount.test.tsimport { applyDiscount } from './discount';
describe('applyDiscount', () => { it('deve calcular corretamente o desconto de 10%', () => { expect(applyDiscount(1000, 10)).toBe(900); });
it('deve retornar o preço original com desconto de 0%', () => { expect(applyDiscount(500, 0)).toBe(500); });
it('deve lançar erro para desconto maior que 100%', () => { expect(() => applyDiscount(100, 150)).toThrow('Porcentagem de desconto inválida'); });});Execute: npx jest. Todos os três testes devem passar em verde.
1.3 Mocks e stubs
Se a função acessa um serviço externo (por exemplo, banco de dados), substituímos por um mock:
// userService.test.tsimport { getUser } from './userService';import { db } from './database';
jest.mock('./database'); // Mock automático
test('getUser retorna usuário por ID', async () => { (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'João' }); const user = await getUser(1); expect(user.name).toBe('João');});2. Testes de integração: verificando a interação entre módulos
Testes de integração verificam como diferentes partes do sistema funcionam juntas: controladores, serviços, repositórios, bancos de dados. Eles são mais lentos que os testes unitários, mas fornecem mais confiança na correção das conexões.
2.1 Testando um endpoint de API com Express
Para testar requisições HTTP, usamos a biblioteca Supertest junto com Jest.
// Instalaçãonpm install --save-dev supertest @types/supertestSuponha que temos uma aplicação Express:
// app.tsimport express from 'express';import { createUser } from './controllers/userController';
export const app = express();app.use(express.json());app.post('/api/users', createUser);Teste de integração:
// app.test.tsimport request from 'supertest';import { app } from './