Capstone: estratégia de testes de um app JS/TS production-grade

TL;DR

Ferramentas isoladas não são uma estratégia. O stack JS moderno se combina no formato do troféu de testes (não a pirâmide clássica): muitos testes de integração (componente + MSW), que dão o melhor retorno confiança/custo em apps de UI; uma base de unit (Vitest) para lógica pura e edge cases; poucos E2E (Playwright) nos caminhos que dão dinheiro; e testes estáticos (TypeScript + lint) como fundação de graça. A regra que rege tudo: teste comportamento, priorize o nível mais barato que dá confiança suficiente, e reserve E2E para o crítico. Este capstone junta as 17 notas numa decisão coerente.

O problema: ter as ferramentas não é ter uma estratégia

Você agora sabe usar Vitest, Testing Library, MSW e Playwright. Mas saber usar cada um não responde a pergunta que importa num app real: o que testar em qual nível, e quanto de cada? Testar tudo em E2E dá uma suíte lenta e flaky; testar tudo em unit com mocks demais dá falsa confiança (passa mas o app quebra); não ter estratégia dá o pior dos dois — retrabalho, buracos e testes que ninguém confia.

Estratégia é alocação: dado orçamento finito de tempo e manutenção, onde investir cada teste para máxima confiança por custo. Este capstone amarra o ferramental do galho à estratégia — que é, no fundo, a teoria de Testes 16 aplicada ao stack JS.

O formato: troféu, não pirâmide

A pirâmide clássica (muitos unit, poucos E2E — Testes 02) foi pensada para uma era em que testes de integração eram lentos e frágeis. No front-end moderno, com Testing Library + MSW, o teste de integração ficou barato e confiável — então o formato ótimo para apps de UI é o troféu de testes (Kent C. Dodds):


graph TB
    E["🏆 E2E (Playwright)<br/>poucos, caminhos críticos"] --> I
    I["INTEGRAÇÃO (Testing Library + MSW)<br/>a maior fatia — melhor ROI"] --> U
    U["UNIT (Vitest)<br/>lógica pura, edge cases"] --> S
    S["ESTÁTICO (TypeScript + ESLint)<br/>fundação de graça"]
    style E fill:#F5A623,color:#000
    style I fill:#4A90D9,color:#fff
    style U fill:#4A90D9,color:#fff
    style S fill:#4A90D9,color:#fff
  • Estático (base, de graça): TypeScript pega classes inteiras de bug antes de qualquer teste rodar; ESLint pega padrões ruins. É o teste mais barato que existe — o compilador rodando.
  • Unit (Vitest): para lógica pura — funções de cálculo, formatação, validação, reducers, edge cases. Rápidos, precisos, muitos.
  • Integração (Testing Library + MSW): a maior fatia. Testar um componente/feature com suas dependências reais (render + interação + rede mockada por MSW) dá a maior confiança por custo — exercita o que o usuário faz, sem a fragilidade do E2E. É o coração da estratégia de UI moderna.
  • E2E (Playwright): poucos, nos caminhos críticos (login, checkout, o fluxo que dá dinheiro). Caros e lentos, mas insubstituíveis para provar que o sistema inteiro se conecta.

Aplicando: o que testar em cada nível

Para uma feature real — digamos, um checkout — a alocação fica:

NívelFerramentaO que testa no checkout
EstáticoTS + ESLinttipos de Pedido, Pagamento; nenhum any solto
UnitVitestcálculo de total, desconto, frete; validação de CEP/cartão (edge cases)
IntegraçãoTesting Library + MSWo form renderiza, valida, e ao submeter chama a API (mockada) e mostra sucesso/erro
E2EPlaywrighto fluxo real: adicionar ao carrinho → checkout → pagamento → confirmação, no browser, contra backend real

Repare: o cálculo de desconto é unit (lógica pura, muitos casos baratos); o comportamento do formulário é integração (o melhor nível para “o usuário preenche e envia”); o fluxo ponta a ponta é um E2E só, o caminho feliz crítico. Testar o cálculo de desconto via E2E seria absurdo (lento, frágil); testar o fluxo inteiro só com unit mockado daria falsa confiança. Cada coisa no seu nível.

Os princípios que atravessam tudo

O galho inteiro se destila em poucos princípios, que são o que você leva para qualquer stack:

  1. Teste comportamento, não implementação (notas 07/08/10). O teste sobrevive à refatoração se afirma o que o usuário percebe, não o useState interno.
  2. Prefira o nível mais barato que dá confiança suficiente. Não suba para E2E o que a integração resolve; não mocke em unit o que a integração testaria melhor.
  3. Determinismo acima de tudo (notas 05/16). Controle tempo (fake timers) e estado (isolamento) — flaky mata a confiança na suíte.
  4. Mocke na camada certa (notas 06/09). vi para módulos, MSW para rede; não reescreva fetch à mão.
  5. Automatize o gate (notas 01/02/17). Budget e suíte na CI, falhando o build — testes que não rodam automaticamente não protegem.
  6. Cobertura é bússola, não meta (nota 12). Detector de buracos, não selo; branches importam, mutation testing prova.

Copiar a distribuição de testes de outro projeto

O que acontece: o time impõe “70% unit, 20% integração, 10% E2E” porque leu num artigo, e acaba com unit demais (mockando tudo, falsa confiança) ou E2E demais (suíte lenta e flaky). Por quê: a distribuição ótima depende do tipo de app — uma lib de utilidades é quase toda unit; um app de UI pesa em integração (troféu); um sistema com muitos fluxos críticos precisa de mais E2E. Número emprestado ignora o seu contexto. Como evitar: decida a alocação pela natureza do código e do risco: lógica pura → unit; comportamento de UI → integração; fluxo crítico de negócio → E2E; e tudo sobre a base estática do TypeScript. Deixe o formato emergir do que dá confiança por custo no seu app.

A estratégia de testes JS em uma frase: combine os níveis no formato do troféu para apps de UI — estático (TS/lint) de graça, unit (Vitest) para lógica pura, a maior fatia em integração (Testing Library + MSW) pelo melhor ROI, e poucos E2E (Playwright) nos caminhos críticos —, sempre testando comportamento, preferindo o nível mais barato que dá confiança suficiente, e rodando tudo na CI de forma determinística.

Em entrevista

“Having the tools isn’t a strategy — the question is what to test at which level and how much. For UI apps I use the testing trophy: static analysis with TypeScript and ESLint as a free foundation; unit tests in Vitest for pure logic and edge cases; the biggest slice in integration — Testing Library plus MSW — because that gives the best confidence-per-cost, exercising what the user does without E2E’s fragility; and a few E2E in Playwright for the money paths like checkout. The pyramid still holds for backend logic; the trophy fits the front end, because Testing Library and MSW made integration cheap. The through-line: test behavior, prefer the cheapest level that gives enough confidence, keep it deterministic, and gate it in CI.”

PTEN
Troféu de testesTesting trophy
Análise estáticaStatic analysis
Confiança por custoConfidence per cost
Alocação de testesTest allocation
Caminho crítico de negócioCritical business path
Nível mais barato suficienteCheapest sufficient level

O que vem a seguir

Você fechou o galho Testes JS — do runner à estratégia. Os caminhos naturais daqui:

  • Testes — a teoria que este galho instrumentou (pirâmide, doubles, TDD, flaky, coverage).
  • Testes — o mesmo capstone no stack Java, para comparar as estratégias.
  • Índice do galho Testes JS — o mapa das 18 notas.

Fontes