Communitygithub.com

Testes React

Testes de componentes React com React Testing Library, Vitest/Jest, MSW para mocking de rede, asserções de acessibilidade com axe e o limite entre testes de componente e testes end-to-end com Playwright/Cypress. Use ao escrever ou corrigir testes para componentes, hooks ou páginas React.

O que é Testes React?

Esta skill define um padrão abrangente e orientado a comportamento para testar aplicações React. O foco central é testar o que o usuário vê e faz, evitando detalhes de implementação. Ela cobre a configuração e uso de React Testing Library, Vitest e Jest, com ênfase em consultas acessíveis (como `getByRole` e `getByLabelText`), interações realistas com `userEvent` e padrões assíncronos robustos. Para mocking de requisições HTTP, a skill adota o MSW (Mock Service Worker) por sua capacidade de interceptar chamadas de rede em nível de protocolo, permitindo que componentes, hooks e bibliotecas de fetch se comportem exatamente como em produção. Além disso, inclui práticas para testes de hooks customizados, asserções de acessibilidade com axe e uma clara diretriz para decidir quando um teste pertence ao React Testing Library (RTL) versus Playwright ou Cypress, tanto em modo de componente quanto E2E. A adoção desta skill resulta em suítes de teste mais confiáveis, significativas e quebram menos com refatorações, pois validam o comportamento observável pelo usuário.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/react-testing

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que Testes React faz?

Padrões abrangentes de teste React para testes de componentes focados em comportamento, testes de hooks customizados, asserções de acessibilidade e mocking em nível de rede.

Quando Ativar

  • Escrevendo testes para componentes React, hooks customizados ou páginas
  • Adicionando cobertura de teste a componentes legados não testados
  • Migrando de padrões Enzyme ou de era de classe para React Testing Library
  • Configurando Vitest ou Jest para um novo projeto React
  • Mockando requisições HTTP em testes
  • Afirmando violações de acessibilidade
  • Decidindo quais testes pertencem ao RTL vs Playwright Component Testing vs E2E completo

Princípio Central

Teste o que o usuário vê e faz, não detalhes de implementação.

Um teste deve:

  • Renderizar o componente com os mesmos provedores que ele tem em produção
  • Interagir com ele por meio de consultas acessíveis (role, label) e userEvent
  • Afirmar saída visível e efeitos colaterais observáveis (callback acionado, requisição enviada)

Um teste NÃO deve:

  • Inspecionar estado do componente, props passadas para filhos ou quais hooks foram chamados
  • Mockar o próprio React ou hooks do framework
  • Afirmar sobre o número de renderizações ou estrutura do DOM além do que afeta os usuários

Escolha da Biblioteca

ExecutorQuandoNota
VitestVite, Remix, configurações modernasMais rápido, ESM nativo, API compatível com Jest
JestNext.js, CRA, repositórios estabelecidosPadrão para muitos projetos React
Playwright Component TestingNecessário motor de navegador realUse quando o JSDOM não tiver o recurso necessário
Cypress Component TestingNavegador real, Cypress já em usoAlternativa ao Playwright CT

Escolha um. Não execute RTL + Vitest E Playwright CT no mesmo repositório a menos que você tenha uma separação de função clara.

Prioridade de Consulta

O React Testing Library expõe consultas em três níveis — use de cima para baixo:

  1. Acessível para todos: getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue
  2. Semântico: getByAltText, getByTitle
  3. IDs de teste (válvula de escape): getByTestId
// Melhor
screen.getByRole("button", { name: /save/i });

// OK para inputs
screen.getByLabelText("Email");

// Último recurso
screen.getByTestId("save-btn");

Variantes:

  • getBy* — lança erro se não encontrar
  • queryBy* — retorna null (use para "afirmar ausência")
  • findBy* — assíncrono, retorna uma Promise (use para elementos que aparecem após trabalho assíncrono)

Interação do Usuário com userEvent

import userEvent from "@testing-library/user-event";

test("submits the form", async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();
  render(<UserForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText("Email"), "[email protected]");
  await user.click(screen.getByRole("button", { name: /save/i }));

  expect(onSubmit).toHaveBeenCalledWith({ email: "[email protected]" });
});
  • Sempre use await nas chamadas de userEvent
  • Chame userEvent.setup() uma vez por teste, reutilize o user retornado
  • userEvent simula uma sequência real de navegador; fireEvent despacha um único evento sintético — prefira userEvent

Padrões Assíncronos

// Elemento que aparece após trabalho assíncrono
expect(await screen.findByText("Loaded")).toBeInTheDocument();

// Afirmação de efeito colateral
await waitFor(() => expect(saveSpy).toHaveBeenCalled());

// Elemento que deve desaparecer
await waitForElementToBeRemoved(() => screen.queryByText("Loading"));

Nunca use setTimeout + asserção — é instável. Use os matchers acima.

Mocking de Rede com MSW

Mock Service Worker faz mocking na camada de rede. O componente, os hooks e a biblioteca de fetch se comportam exatamente como em produção.

Configuração

// test/setup.ts
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/users/:id", ({ params }) =>
    HttpResponse.json({ id: params.id, name: "Alice" }),
  ),
  http.post("/api/users", async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: "new-id", ...body }, { status: 201 });
  }),
];

export const server = setupServer(...handlers);

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Configure onUnhandledRequest: "error" para que qualquer requisição não mockada falhe o teste de forma ruidosa — aprovações silenciosas são piores que vermelhos.

Sobrescrita por teste

test("renders error on 500", async () => {
  server.use(
    http.get("/api/users/:id", () => new HttpResponse(null, { status: 500 })),
  );
  render(<UserPage id="1" />);
  expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
});

Encapsulamento com Provedores

Encapsule os provedores uma vez em um test-utils.tsx:

// test-utils.tsx
import { render, RenderOptions } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export function renderWithProviders(
  ui: React.ReactElement,
  options?: RenderOptions,
) {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  return render(
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={lightTheme}>
        <MemoryRouter>{ui}</MemoryRouter>
      </ThemeProvider>
    </QueryClientProvider>,
    options,
  );
}

export * from "@testing-library/react";

Em seguida, use import { renderWithProviders, screen } from "test-utils" em cada arquivo de teste.

Teste de Hooks Customizados

import { renderHook, act } from "@testing-library/react";

test("useCounter increments and decrements", () => {
  const { result } = renderHook(() => useCounter(0));

  expect(result.current.count).toBe(0);

  act(() => result.current.increment());
  expect(result.current.count).toBe(1);

  act(() => result.current.decrement());
  expect(result.current.count).toBe(0);
});

test("useCounter accepts initial value", () => {
  const { result } = renderHook(() => useCounter(10));
  expect(result.current.count).toBe(10);
});

test("useUser fetches user data", async () => {
  // Instancie o QueryClient UMA VEZ por teste fora do wrapper para que sobreviva a re-renderizações.
  // Criá-lo dentro da closure do wrapper redefine o estado do cache em cada renderização, produzindo testes instáveis.
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );

  const { result } = renderHook(() => useUser("1"), { wrapper });

  await waitFor(() => expect(result.current.isSuccess).toBe(true));
  expect(result.current.data).toEqual({ id: "1", name: "Alice" });
});
  • Encapsule chamadas que alteram estado em act
  • Teste apenas por meio da API pública do hook
  • Para hooks que usam contexto, passe um wrapper

Asserções de Acessibilidade

import { axe, toHaveNoViolations } from "jest-axe"; // ou vitest-axe
expect.extend(toHaveNoViolations);

test("UserCard has no a11y violations", async () => {
  const { container } = render(<UserCard user={mockUser} />);
  expect(await axe(container)).toHaveNoViolations();
});

Execute axe em testes de componente para cada componente interativo. Captura:

  • Labels ausentes em inputs de formulário
  • Uso inválido de ARIA
  • Contraste de cor pobre (limitado — JSDOM não tem motor CSS real, portanto isso funciona apenas para estilos inline; contraste visual pertence ao Playwright)
  • Texto alternativo ausente em imagens
  • Violações de ordem de cabeçalho

Link cruzado: skills/accessibility/SKILL.md para o manual mais amplo de testes de acessibilidade.

Quando NÃO Usar Testes de Snapshot

Snapshots de saída renderizada:

  • Quebram a cada mudança de estilo
  • Recebem carimbo de borracha durante a revisão
  • Testam detalhe de implementação (estrutura do DOM), não comportamento

Usos aceitáveis de snapshot:

  • Funções de serialização de dados puros (formatInvoice(invoice) -> string estável)
  • Arquivos de configuração gerados (por exemplo, saída de configuração do webpack)

Para regressão visual em componentes, use screenshots do Playwright/Cypress ou Percy/Chromatic — diffs visuais reais, não strings de DOM.

Quando Usar Playwright / Cypress

JSDOM (usado pelo Vitest/Jest) não pode:

  • Renderizar layout real (flexbox, grid, consultas de viewport)
  • Executar animações nativas de navegador, transições CSS
  • Testar comportamento de rolagem, arrastar e soltar, colar da área de transferência
  • Lidar com iframes, popups, downloads, fluxos de origem cruzada
  • Executar rede real em um ambiente controlado com suporte completo a DevTools

Para qualquer um desses, use Playwright Component Testing (teste de componente em navegador real) ou E2E completo. Veja e2e-testing skill.

Limite de decisão:

  • Um hook, um componente de apresentação, um formulário com lógica -> RTL
  • Um componente cujo layout importa ou que usa APIs de navegador não presentes no JSDOM -> Playwright CT
  • Um fluxo de usuário completo em várias páginas -> Playwright/Cypress E2E

Metas de Cobertura

CamadaMeta
Utilitários puros>=90%
Hooks customizados>=85%
Componentes de apresentação>=80% — comportamento, não linhas
Componentes container>=70% — caminhos principais + estados de erro
PáginasE2E coberto separadamente; mínimo de teste de fumaça

Configure via vitest.config.ts / jest.config.js:

// vitest.config.ts
test: {
  coverage: {
    provider: "v8",
    reporter: ["text", "html", "lcov"],
    thresholds: {
      lines: 80,
      functions: 80,
      branches: 70,
      statements: 80,
    },
  },
}

Antipadrões

  • container.querySelector("...") — ignora consultas de acessibilidade, permite que testes passem quando usuários reais falhariam
  • Afirmar sobre número de renderizações — detalhe de implementação
  • jest.mock("react", ...) — nunca mocke React. Refatore o componente
  • Mockar componentes filhos por padrão — testa a integração, não o isolamento. Mock apenas quando o filho tem efeitos colaterais pesados
  • Ignorar avisos de act() — eles sinalizam bugs reais (atualização de estado após desmontagem, encapsulamento assíncrono ausente)
  • Compartilhar estado mutável entre testes — torna-se instável quando a ordem dos testes muda
  • Testes que passam com it.skip() removido — seu teste não afirma realmente o que você pensa

Fluxo de Trabalho TDD

RED     -> Escreva teste falhando para o próximo requisito
GREEN   -> Escreva código mínimo do componente para passar
REFACTOR -> Melhore o componente, os testes permanecem verdes
REPEAT  -> Próximo requisito

Para novos componentes:

  1. Defina o tipo de prop e a assinatura do componente
  2. Escreva o primeiro teste para o caso mais simples
  3. Verifique se ele falha pelo motivo correto
  4. Implemente o suficiente para passar
  5. Adicione o próximo caso de teste
  6. Refatore quando o terceiro teste semelhante revelar um padrão

Comandos de Teste

# Vitest
vitest                            # watch
vitest run                        # execução única
vitest run --coverage             # com cobertura
vitest run path/to/file.test.tsx  # arquivo único

# Jest
jest --watch
jest --coverage
jest path/to/file.test.tsx

# Modo CI
CI=true vitest run --coverage

Relacionados

Exemplos

Envio de formulário com MSW e userEvent

test("submits user form and shows success", async () => {
  server.use(
    http.post("/api/users", () =>
      HttpResponse.json({ id: "1", name: "Alice" }, { status: 201 }),
    ),
  );

  const user = userEvent.setup();
  renderWithProviders(<UserForm />);

  await user.type(screen.getByLabelText("Name"), "Alice");
  await user.type(screen.getByLabelText("Email"), "[email protected]");
  await user.click(screen.getByRole("button", { name: /save/i }));

  expect(await screen.findByText(/saved successfully/i)).toBeInTheDocument();
});

Testando um error boundary

function Broken() {
  throw new Error("boom");
}

test("error boundary renders fallback", () => {
  // Suprime o ruído de console.error do React para o erro esperado e restaura em seguida para
  // que o spy não vaze entre testes e esconda erros reais em outro lugar.
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
  try {
    render(
      <ErrorBoundary fallback={<div>Something went wrong</div>}>
        <Broken />
      </ErrorBoundary>,
    );

    expect(screen.getByText("Something went wrong")).toBeInTheDocument();
  } finally {
    errorSpy.mockRestore();
  }
});

Testando um boundary Suspense

test("shows loading then content", async () => {
  renderWithProviders(
    <Suspense fallback={<div>Loading...</div>}>
      <UserDetail id="1" />
    </Suspense>,
  );

  expect(screen.getByText("Loading...")).toBeInTheDocument();
  expect(await screen.findByText("Alice")).toBeInTheDocument();
});

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Habilidades Relacionadas