Testar TypeScript com Jest:
Configure Jest com TypeScript do jeito certo. De ts-jest e jest.config.ts até mocks tipados e testes assíncronos, tudo com exemplos práticos.
Por que isso é importante
Testar TypeScript com Jest:. Configure Jest com TypeScript do jeito certo. De ts-jest e jest.config.ts até mocks tipados e testes assíncronos, tudo com exemplos práticos.
Por Que Testar TypeScript com Jest
Jest é o framework de testes mais popular do ecossistema JavaScript. Roda testes em paralelo, tem mocks embutidos, cobertura de código nativa e uma API que qualquer dev aprende em minutos. O problema: ele foi feito pra JavaScript.
TypeScript precisa ser transpilado antes de executar. O Jest não sabe ler arquivos .ts nativamente. É aí que entra o ts-jest — um transformer que compila TypeScript on-the-fly durante a execução dos testes. Sem step de build separado.
A vantagem de testar com TypeScript é que seus mocks, fixtures e assertions também são tipados. Se uma função muda a assinatura, o teste quebra na compilação, não em runtime. Isso pega erros que em JavaScript só apareceriam quando o CI rodasse.
Como Configurar Jest com TypeScript Passo a Passo
Setup completo do zero. Cada passo te leva de um projeto sem testes a uma suíte rodando com tipagem completa.
- Passo 1 - Instale as dependências: npm install -D jest ts-jest @types/jest. O ts-jest faz a transpilação, e @types/jest dá as tipagens pra describe, it, expect e todos os matchers.
- Passo 2 - Crie o jest.config.ts: Use npx ts-jest config:init pra gerar a config base, ou crie manualmente com preset: 'ts-jest' e testEnvironment: 'node'.
- Passo 3 - Configure os scripts no package.json: Adicione "test": "jest", "test:watch": "jest --watch" e "test:coverage": "jest --coverage" nos scripts.
- Passo 4 - Crie a pasta de testes: Use __tests__/ na raiz ou coloque arquivos .test.ts junto dos módulos. O Jest detecta ambos os padrões automaticamente.
- Passo 5 - Escreva o primeiro teste: Crie um arquivo .test.ts, importe a função a ser testada e use describe/it/expect. Rode npm test e veja o resultado.
- Passo 6 - Configure path aliases no Jest: Se seu tsconfig tem paths, espelhe no moduleNameMapper do jest.config pra evitar erros de resolução de módulo.
Configuração Completa do jest.config.ts
Vamos à configuração que funciona em 95% dos projetos TypeScript.
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
// ts-jest transpila TypeScript on-the-fly
preset: 'ts-jest',
// 'node' pra backend, 'jsdom' pra frontend
testEnvironment: 'node',
// Onde ficam os testes
roots: ['<rootDir>/src'],
// Padrões de arquivo de teste
testMatch: [
'**/__tests__/**/*.test.ts',
'**/*.spec.ts',
],
// Path aliases (espelhar tsconfig.json)
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
// Cobertura de código
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/index.ts',
],
// Limites de cobertura
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
export default config;
O coverageThreshold é opcional, mas recomendado. Ele faz o Jest falhar se a cobertura cair abaixo de 80%. Funciona como uma trava de segurança no CI: ninguém merga código que reduz a cobertura.
Escrevendo Testes Tipados
A grande vantagem de testar com TypeScript é ter tipagem nos testes. Veja como isso funciona na prática.
Teste Básico com Tipagem
// src/utils/math.ts
export function soma(a: number, b: number): number {
return a + b;
}
export function divide(a: number, b: number): number {
if (b === 0) throw new Error('Divisão por zero');
return a / b;
}
// src/utils/__tests__/math.test.ts
import { soma, divide } from '../math';
describe('math utils', () => {
describe('soma', () => {
it('soma dois números positivos', () => {
const resultado: number = soma(2, 3);
expect(resultado).toBe(5);
});
it('soma números negativos', () => {
expect(soma(-1, -2)).toBe(-3);
});
it('soma com zero', () => {
expect(soma(5, 0)).toBe(5);
});
});
describe('divide', () => {
it('divide corretamente', () => {
expect(divide(10, 2)).toBe(5);
});
it('lança erro ao dividir por zero', () => {
expect(() => divide(10, 0)).toThrow('Divisão por zero');
});
});
});
Testando Interfaces e Tipos Complexos
// src/models/user.ts
export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
export function createUser(data: Omit<User, 'id'>): User {
return {
id: crypto.randomUUID(),
...data,
};
}
export function isAdmin(user: User): boolean {
return user.role === 'admin';
}
// src/models/__tests__/user.test.ts
import { User, createUser, isAdmin } from '../user';
describe('User model', () => {
const mockUserData: Omit<User, 'id'> = {
name: 'Maria Silva',
email: 'maria@email.com',
role: 'admin',
};
it('cria usuário com id gerado', () => {
const user = createUser(mockUserData);
expect(user.id).toBeDefined();
expect(user.name).toBe('Maria Silva');
expect(user.email).toBe('maria@email.com');
expect(user.role).toBe('admin');
});
it('identifica admin corretamente', () => {
const admin = createUser({ ...mockUserData, role: 'admin' });
const regular = createUser({ ...mockUserData, role: 'user' });
expect(isAdmin(admin)).toBe(true);
expect(isAdmin(regular)).toBe(false);
});
});
Repare como os tipos guiam os testes. Se createUser mudar a interface, os testes quebram na compilação, não na execução. Esse feedback rápido é o que faz TypeScript + Jest ser tão produtivo.
Mocks Tipados no Jest
Mocks são a parte mais confusa de testar com TypeScript. O Jest tem jest.fn() e jest.mock(), mas sem tipagem eles perdem toda a segurança. Veja como tipar mocks corretamente.
// src/services/email.ts
export interface EmailService {
send(to: string, subject: string, body: string): Promise<boolean>;
}
// src/services/notification.ts
import { EmailService } from './email';
export class NotificationService {
constructor(private emailService: EmailService) {}
async notifyUser(email: string, message: string): Promise<boolean> {
return this.emailService.send(
email,
'Nova Notificação',
message
);
}
}
// src/services/__tests__/notification.test.ts
import { NotificationService } from '../notification';
import { EmailService } from '../email';
describe('NotificationService', () => {
// Mock tipado da interface EmailService
const mockEmailService: jest.Mocked<EmailService> = {
send: jest.fn(),
};
const service = new NotificationService(mockEmailService);
beforeEach(() => {
jest.clearAllMocks();
});
it('envia email com dados corretos', async () => {
mockEmailService.send.mockResolvedValue(true);
const result = await service.notifyUser(
'user@email.com',
'Olá!'
);
expect(result).toBe(true);
expect(mockEmailService.send).toHaveBeenCalledWith(
'user@email.com',
'Nova Notificação',
'Olá!'
);
});
it('retorna false quando email falha', async () => {
mockEmailService.send.mockResolvedValue(false);
const result = await service.notifyUser(
'user@email.com',
'Teste'
);
expect(result).toBe(false);
});
});
O jest.Mocked
Testando Funções Assíncronas
A maioria das funções reais é assíncrona: chamadas de API, queries no banco, leitura de arquivos. Testar async/await com Jest e TypeScript exige atenção com tipos e tratamento de erro.
// src/api/users.ts
import { User } from '@/models/user';
export async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Usuário ${id} não encontrado`);
}
return response.json();
}
export async function fetchUsers(): Promise<User[]> {
const response = await fetch('/api/users');
return response.json();
}
// src/api/__tests__/users.test.ts
import { fetchUser, fetchUsers } from '../users';
// Mock global do fetch
global.fetch = jest.fn();
const mockFetch = fetch as jest.MockedFunction<typeof fetch>;
describe('users API', () => {
beforeEach(() => {
mockFetch.mockReset();
});
describe('fetchUser', () => {
it('retorna usuário quando API responde 200', async () => {
const mockUser = {
id: '1',
name: 'João',
email: 'joao@email.com',
role: 'user' as const,
};
mockFetch.mockResolvedValue({
ok: true,
json: async () => mockUser,
} as Response);
const user = await fetchUser('1');
expect(user).toEqual(mockUser);
expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
});
it('lança erro quando API responde 404', async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 404,
} as Response);
await expect(fetchUser('999'))
.rejects
.toThrow('Usuário 999 não encontrado');
});
});
describe('fetchUsers', () => {
it('retorna lista de usuários', async () => {
const mockUsers = [
{ id: '1', name: 'Ana', email: 'ana@email.com', role: 'admin' },
{ id: '2', name: 'Pedro', email: 'pedro@email.com', role: 'user' },
];
mockFetch.mockResolvedValue({
ok: true,
json: async () => mockUsers,
} as Response);
const users = await fetchUsers();
expect(users).toHaveLength(2);
expect(users[0].name).toBe('Ana');
});
});
});
O padrão expect(...).rejects.toThrow() testa erros em funções async sem precisar de try/catch no teste. Limpo e direto. Use mockResolvedValue pra simular respostas de sucesso e mockRejectedValue pra simular falhas de rede.
Utilities de Teste Tipadas
Conforme o projeto cresce, você repete código nos testes: fixtures, factories, helpers. Criar utilities tipadas centraliza essa lógica e garante consistência.
// src/__tests__/helpers/factories.ts
import { User } from '@/models/user';
// Factory com valores padrão e override tipado
export function createMockUser(
overrides: Partial<User> = {}
): User {
return {
id: 'test-id-123',
name: 'Test User',
email: 'test@email.com',
role: 'user',
...overrides,
};
}
// Factory pra listas
export function createMockUsers(count: number): User[] {
return Array.from({ length: count }, (_, i) =>
createMockUser({
id: `test-id-${i}`,
name: `User ${i}`,
email: `user${i}@email.com`,
})
);
}
// Uso nos testes:
import { createMockUser, createMockUsers } from '../helpers/factories';
it('processa admin corretamente', () => {
// Override só o que importa pro teste
const admin = createMockUser({ role: 'admin' });
expect(isAdmin(admin)).toBe(true);
});
it('lista 10 usuários', () => {
const users = createMockUsers(10);
expect(users).toHaveLength(10);
expect(users[0].email).toBe('user0@email.com');
});
Factories com Partial
Erros Comuns ao Testar TypeScript com Jest
Armadilhas nos testes
Não instalar @types/jest: sem esse pacote, describe, it e expect aparecem como undefined. O TypeScript não sabe que esses globais existem sem os types.
Usar jest.fn() sem tipar: jest.fn() retorna jest.Mock
Esquecer de limpar mocks entre testes: sem beforeEach(() => jest.clearAllMocks()), os mocks acumulam chamadas. Um teste interfere no outro e os resultados ficam inconsistentes.
Mock de módulo com tipagem errada: jest.mock('./modulo') faz o mock funcionar, mas os tipos do import original continuam valendo. Cast o import com as jest.Mocked
Não tratar rejeições de Promise: se uma função async lança erro e o teste não trata com rejects.toThrow(), o Jest marca como falha por timeout em vez de mostrar o erro real. Sempre teste os cenários de erro.
Checklist de Testes TypeScript com Jest
- jest, ts-jest e @types/jest instalados como devDependencies
- jest.config.ts criado com preset ts-jest
- Scripts test, test:watch e test:coverage no package.json
- Path aliases espelhados no moduleNameMapper
- Mocks tipados com jest.Mocked
- beforeEach com jest.clearAllMocks() em cada describe
- Funções async testadas com expect.rejects pra erros
- Factories tipadas criadas pra fixtures reutilizáveis
- Cobertura acima de 80% com coverageThreshold configurado
Testes Profissionais em TypeScript
Testar código TypeScript com Jest é uma skill que separa devs júniors de profissionais. No CrazyStack, cada módulo tem testes tipados — mocks, factories, integração. Você aprende a construir uma suíte de testes que dá confiança real pra refatorar e fazer deploy.
Código testado é código confiável. Se quer evoluir como dev, testes são o caminho mais curto.
Perguntas frequentes
Por Que Testar TypeScript com Jest
Jest é o framework de testes mais popular do ecossistema JavaScript. Roda testes em paralelo, tem mocks embutidos, cobertura de código nativa e uma API que qualquer dev aprende em minutos. O problema: ele foi feito pra JavaScript. TypeScript precisa ser transpilado antes de executar. O Jest não sabe ler arquivos .ts nativamente. É aí que entra o ts-jest — um transformer que compila TypeScript on-the-fly durante a execução dos testes. Sem step de build separado. A vantagem de testar com TypeScript é que seus mocks, fixtures e assertions também são tipados. Se uma função muda a assinatura, o teste quebra na compilação, não em runtime. Isso pega erros que em JavaScript só apareceriam quando o CI rodasse.
Como Configurar Jest com TypeScript Passo a Passo
Setup completo do zero. Cada passo te leva de um projeto sem testes a uma suíte rodando com tipagem completa.