TDD na prática com Vitest: do zero à cobertura real
Como aplicar Test-Driven Development no dia a dia usando Vitest, com exemplos reais de módulos frontend complexos.
Por que TDD ainda importa em 2025
Muito se fala sobre TDD como uma prática "acadêmica" que não sobrevive ao mundo real. Mas depois de aplicar consistentemente na Zenvia — em módulos como o Convert (chat multicanal) e Nutrir (automações) — posso dizer com confiança: TDD não é sobre escrever testes, é sobre design de software.
Quando você escreve o teste antes do código, você é forçado a pensar na interface pública do seu módulo antes de pensar na implementação. Isso resulta naturalmente em código mais desacoplado e testável.
Setup inicial com Vitest
pnpm add -D vitest @testing-library/react @testing-library/user-event jsdom
No vite.config.ts (ou vitest.config.ts):
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
},
});
O ciclo Red → Green → Refactor
O ciclo fundamental do TDD é simples:
- Red: escreva um teste que falha
- Green: escreva o código mínimo para passar
- Refactor: melhore sem quebrar os testes
Exemplo: validação de formulário de contato
Começando pelo teste:
// src/validation/contactForm.test.ts
import { describe, it, expect } from 'vitest';
import { validateContactForm } from './contactForm';
describe('validateContactForm', () => {
it('deve retornar erro quando nome está vazio', () => {
const result = validateContactForm({ name: '', email: '[email protected]', message: 'oi' });
expect(result.errors.name).toBe('Nome é obrigatório');
});
it('deve retornar sucesso quando todos campos são válidos', () => {
const result = validateContactForm({
name: 'Breno',
email: '[email protected]',
message: 'Quero contratar',
});
expect(result.success).toBe(true);
});
});
Agora a implementação mínima:
// src/validation/contactForm.ts
export function validateContactForm(data: { name: string; email: string; message: string }) {
const errors: Record<string, string> = {};
if (!data.name.trim()) errors.name = 'Nome é obrigatório';
if (!data.email.includes('@')) errors.email = 'E-mail inválido';
if (!data.message.trim()) errors.message = 'Mensagem é obrigatória';
return {
success: Object.keys(errors).length === 0,
errors,
};
}
Cobertura de testes que realmente importa
Cobertura de 100% não significa nada se você está testando implementação em vez de comportamento. Foque em:
- Casos de borda: valores vazios, nulos, extremos
- Fluxos críticos de negócio: o que quebra se falhar?
- Regressões: adicione testes para cada bug corrigido
pnpm vitest run --coverage
Conclusão
TDD te torna mais lento no início e muito mais rápido no médio e longo prazo. A segurança de ter uma suíte de testes sólida permite refatorações ousadas sem medo. Comece pequeno: uma função pura, um hook, uma validação. O hábito se constrói gradualmente.