Front End

31 mar, 2026

7 passos para criar testes de front-end que realmente funcionam

Publicidade

Vamos falar a real? Boa parte dos testes de front-end hoje são… inúteis. Eles quebram por qualquer mudança visual, não pegam bugs reais e viram custo, não valor. E o pior: dão falsa sensação de segurança.

Se você quer jogar no nível sênior, precisa mudar o jeito de testar.

Esse guia é direto ao ponto: 7 passos práticos para criar testes que realmente fazem sentido.

1. Pare de testar implementação (e comece a testar comportamento)

Esse é o erro mais comum.

❌ Testando implementação

import { render } from '@testing-library/react';
import Button from './Button';

test('deve renderizar botão com classe primary', () => {
  const { container } = render(<Button />);

  expect(container.firstChild).toHaveClass('btn-primary');
});

Problema:

👉 qualquer refactor quebra o teste
👉 mesmo sem alterar comportamento

✅ Testando comportamento

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';

test('deve disparar ação ao clicar', async () => {
  const handleClick = jest.fn();

  render(<Button onClick={handleClick}>Enviar</Button>);

  await userEvent.click(screen.getByText('Enviar'));

  expect(handleClick).toHaveBeenCalled();
});

Agora você testa o que importa:

👉 o que o usuário faz

2. Evite snapshot testing (na maioria dos casos)

Snapshot parece bonito.

Mas na prática…

👉 vira um dump gigante impossível de revisar

❌ Snapshot inútil

import { render } from '@testing-library/react';

test('snapshot do componente', () => {
  const { container } = render(<Card />);

  expect(container).toMatchSnapshot();
});

Problema real

  • ninguém revisa snapshot
  • qualquer mudança quebra
  • dev só dá “update snapshot”

👉 virou ritual, não validação

Quando usar snapshot?

  • componentes muito estáveis
  • estruturas críticas (raros casos)

Fora isso…

👉 evite sem dó

3. Teste fluxos reais (não funções isoladas)

Usuário não usa função.

Usuário usa fluxo.

❌ Teste artificial

test('função soma', () => {
  expect(sum(2, 2)).toBe(4);
});

Legal…

Mas isso não garante nada na UI.

✅ Teste de fluxo real

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Login from './Login';

test('usuário consegue fazer login', async () => {
  render(<Login />);

  await userEvent.type(screen.getByLabelText('Email'), 'teste@email.com');
  await userEvent.type(screen.getByLabelText('Senha'), '123456');

  await userEvent.click(screen.getByRole('button', { name: 'Entrar' }));

  expect(await screen.findByText('Bem-vindo')).toBeInTheDocument();
});

Agora sim:

👉 você validou o que realmente importa

4. Use testes E2E para o que realmente importa

Ferramentas como Playwright e Cypress entram aqui.

Exemplo com Playwright

import { test, expect } from '@playwright/test';

test('fluxo de compra completo', async ({ page }) => {
  await page.goto('http://localhost:3000');

  await page.click('text=Produtos');
  await page.click('text=Adicionar ao carrinho');

  await page.click('text=Finalizar compra');

  await expect(page.locator('h1')).toHaveText('Pedido confirmado');
});

Quando usar E2E

  • fluxos críticos (checkout, login)
  • integração entre páginas
  • validação real do sistema

Quando NÃO usar

  • testar cada botão
  • testar lógica isolada

👉 E2E é caro — use com inteligência

5. Testes de contrato: o segredo pouco usado

Esse aqui é nível sênior.

Problema

Frontend depende da API.

Se backend muda…

👉 tudo quebra

Solução: contrato

// contrato esperado
type User = {
  id: number;
  name: string;
};

Testando contrato

import { fetchUsers } from './api';

test('API retorna usuários no formato correto', async () => {
  const users = await fetchUsers();

  expect(users).toEqual(
    expect.arrayContaining([
      expect.objectContaining({
        id: expect.any(Number),
        name: expect.any(String),
      }),
    ])
  );
});

👉 você garante que backend não quebrou seu front

6. Mock com responsabilidade (sem virar mentira)

Mock é útil.

Mas também é perigoso.

❌ Mock enganoso

jest.mock('./api', () => ({
  fetchUsers: () => Promise.resolve([{ id: 1, name: 'Fake' }]),
}));

👉 você está testando um mundo que não existe

✅ Mock mais realista

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'Rafa' },
        { id: 2, name: 'Dev' },
      ])
    );
  })
);

beforeAll(() => server.listen());
afterAll(() => server.close());

👉 simula comportamento real da API

7. Teste menos, mas melhor

Esse é o passo mais difícil.

Anti-pattern clássico

  • 300 testes
  • lentos
  • frágeis
  • ninguém confia

Abordagem sênior

  • poucos testes
  • focados em fluxo
  • confiáveis
  • fáceis de manter

Estratégia prática

Se você quiser algo equilibrado:

  • Testes unitários → lógica crítica
  • Testes de integração → componentes
  • E2E → fluxos principais

O que realmente diferencia um sênior

Não é saber ferramenta. É saber o que NÃO testar, porque no fim:

👉 teste bom pega bug
👉 teste ruim só ocupa tempo

Conclusão: confiança, não cobertura

Cobertura alta não significa qualidade.

O que importa é: confiança no deploy, segurança para refatorar e bugs pegos antes da produção.

Se seus testes não fazem isso… eles não estão cumprindo o papel deles.