Testes Avançados em TypeScript: de Unit a E2E
Testes não são apenas uma verificação de funcionalidade do código, mas a base do desenvolvimento profissional em TypeScript. No nível avançado, passamos de simples asserts para a construção de pirâmides de teste isoladas, rápidas e confiáveis. Neste artigo, abordaremos abordagens práticas para testes unitários, de integração e e2e usando as melhores ferramentas do ecossistema TypeScript.
1. Testes Unitários Avançados com Jest e TypeScript
Testes unitários verificam módulos isolados. Em TypeScript, a principal dificuldade é lidar com dependências, tipos e assincronicidade.
1.1. Mocks e tipagem com jest.mocked
Use jest.mocked para tipagem completa de mocks. Isso garante que você não chamará um método inexistente.
import { fetchUserData } from './api';import { processUser } from './userProcessor';
jest.mock('./api');
const mockedFetchUserData = jest.mocked(fetchUserData);
describe('processUser', () => { it('deve processar os dados do usuário', async () => { const mockUser = { id: 1, name: 'Alice', email: 'alice@test.com' }; mockedFetchUserData.mockResolvedValue(mockUser);
const result = await processUser(1);
expect(result).toEqual({ displayName: 'Alice', contact: 'alice@test.com' }); expect(mockedFetchUserData).toHaveBeenCalledWith(1); });
it('deve lançar um erro quando o email estiver ausente', async () => { mockedFetchUserData.mockResolvedValue({ id: 2, name: 'Bob', email: null }); await expect(processUser(2)).rejects.toThrow('Email is required'); });});1.2. Testando tipos complexos e generics
Para funções com genéricos, crie tipos auxiliares de teste para evitar duplicação.
// Códigofunction getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
// Testetype TestArray = number[];const array: TestArray = [10, 20, 30];
describe('getFirstElement', () => { it('deve retornar o primeiro elemento do array de números', () => { const result = getFirstElement(array); expect(result).toBe(10); // TypeScript infere automaticamente o tipo number });
it('deve retornar undefined para um array vazio', () => { const result = getFirstElement([]); expect(result).toBeUndefined(); });});2. Testes de Integração: Banco de Dados e API
Testes de integração verificam a interação entre módulos: controllers, services e banco de dados. Para aplicações TypeScript em Node.js, a combinação ideal é Jest + Supertest + Testcontainers.
2.1. Configuração do banco de dados de teste com Testcontainers
Testcontainers sobe um PostgreSQL no Docker, garantindo um ambiente limpo para cada teste.
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';import { Client } from 'pg';
let container: StartedPostgreSqlContainer;let client: Client;
beforeAll(async () => { container = await new PostgreSqlContainer().start(); client = new Client({ connectionString: container.getConnectionUri() }); await client.connect(); await client.query(` CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); `);}, 30000); // timeout de 30 segundos para baixar a imagem
afterAll(async () => { await client.end(); await container.stop();});
describe('User Repository', () => { it('deve criar e retornar um usuário', async () => { const res = await client.query( `INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *`, ['Test', 'test@example.com'] ); expect(res.rows[0].name).toBe('Test'); });});2.2. Testando REST API com Supertest
Iniciamos a aplicação Express e test