Desenvolvimento Web

Testes automatizados em JavaScript: Jest, Vitest e Testing Library na prática real

Testes automatizados em JavaScript: Jest, Vitest e Testing Library na prática real

Código sem testes é código que o autor confia que funciona hoje, mas não tem garantia de que continuará funcionando amanhã. Testes automatizados não são burocracia ou overhead — são a única forma de refatorar com confiança, onboardar novos desenvolvedores rapidamente, e a construir produtos que crescem sem acumular dívida técnica. O ecossistema JavaScript de testes madureceu: Vitest está substituindo Jest, e Testing Library estabeleceu o padrão de como testar componentes de interface.

A pirâmide de testes na prática

A pirâmide de testes (muitos unitários, menos integração, poucos E2E) existe por razões práticas de velocidade e confiabilidade. Testes unitários rodam em milissegundos, são determinísticos, e isolam falhas com precisão cirúrgica. Testes de integração verificam contratos entre camadas reais (componente + estado, API + banco). Testes E2E (Playwright, Cypress) verificam a experiência completa do usuário, mas são lentos, frágeis em CI, e difíceis de debugar. Inverta o antipadrão comum do copo de sorvete (muitos E2E, poucos unitários) e adicione E2E apenas para os fluxos críticos de negócio.

O que merece testes unitários? Lógica pura: funções de transformação de dados, validadores, formatadores, algoritmos. O que merece testes de integração? Componentes React com suas dependências reais (hooks, contexto, chamadas de API mockadas), handlers de rotas com banco de dados real (em ambiente de teste), e serviços que coordinam múltiplos módulos. O que merece E2E? Fluxo de checkout, fluxo de onboarding, login, e os 3-5 caminhos mais críticos para o negócio. Tudo o mais risco de ser sobre-engenhado para um ganho marginal.

Vitest: o Jest moderno

Vitest usa a mesma API do Jest (describe, it, expect, vi.mock), mas é construído sobre Vite — compartilhando a mesma configuração, sem setup separado para paths aliases, TypeScript e ESM. Rodar testes com vitest usa Hot Module Replacement para re-executar apenas os testes afetados por mudanças, reduzindo o ciclo de feedback de segundos para milissegundos. Coverage com @vitest/coverage-v8 integra com o mesmo pipeline de build sem configuração extra.

Mocking no Vitest/Jest é uma das features mais poderosas e mal usadas. vi.mock('axios') substitui o módulo inteiro por um mock automático. vi.spyOn(objeto, 'método') observa chamadas sem substituir a implementação. vi.fn() cria funções mock que registram chamadas e permitem programar retornos. A armadilha mais comum: mockar demais e acabar testando o mock, não o código real. A regra prática: mocke apenas na fronteira do sistema (chamadas HTTP, banco de dados, filesystem) — código de negócio deve ser testado com implementações reais.

Testing Library: testando como o usuário usa

Testing Library (React Testing Library, Vue Testing Library, etc.) é baseado no princípio de Guiding Principle: testes devem refletir como o software é usado, não detalhes de implementação. Em vez de acessar state interno ou propriedades do componente React, você interage via DOM como um usuário real faria: getByRole('button', { name: 'Enviar' }), getByLabelText('Email'), getByText('Sucesso!'). Quando o componente é refatorado internamente mas continua funcionando igual, os testes continuam passando — não quebram por mudança de nome de prop ou de estrutura interna.

userEvent simula interações reais de usuário: userEvent.click(), userEvent.type(), userEvent.selectOptions(). Prefira userEvent sobre fireEvent — ele simula toda a sequência de eventos que um clique real dispara (mousedown, mouseup, click, focus), tornando testes mais realistas. Para código assíncrono (loading states, chamadas de API), await waitFor(() => expect(...)) e findByText() esperam o DOM atualizar. MSW (Mock Service Worker) mocka o service worker real no browser — suas chamadas fetch reais são interceptadas e respondidas com dados mockados, sem mudar uma linha do código da aplicação.

Estratégias para aumentar cobertura sem dívida

Coverage de 100% é fetiche, não objetivo. Cobertura de linhas não garante qualidade: um teste que cobre uma linha sem verificar o resultado dela é teatral. Priorize: funções puras (custo de teste zero, valor alto), casos de borda de lógica crítica (limites de números, strings vazias, arrays vazios, null), e caminhos de erro (o que acontece quando a API falha? quando o banco não responde?). Esses são os testes que evitam incidentes de produção, não os que aumentam o número de coverage.

Test-Driven Development (TDD) tem valor real para lógica complexa: escrever o teste antes do código força clareza de interface (como a função deveria ser usada?) antes de pensar em implementação. Red-Green-Refactor: escreva um teste que falha (red), implemente o mínimo para passar (green), melhore a implementação sem quebrar o teste (refactor). TDD não é mandatório para todo código — é uma ferramenta especialmente eficaz para algoritmos, parsers, e lógica de negócio com muitos edge cases. Para UI e integrações, teste-depois-de-implementar continua sendo pragmático e eficaz.

Tem um projeto em mente?

Somos especialistas em transformar ideias em produtos digitais. Apps, sites, automações e IA — vamos construir juntos.

Resposta rápida Orçamento sem compromisso +100 projetos entregues
Compartilhar: