af aprenda frontend
módulo 07 qualidade

Mocks e stubs — controlar dependências.

Quando substituir dependências externas (rede, tempo, módulos) e como fazê-lo com vi.fn() e vi.mock(). Riscos de mockar demais.

Testes que dependem da rede, do horário atual ou de um banco de dados são lentos, instáveis e difíceis de depurar. Mocks são substitutos controlados para essas dependências — você define o que elas retornam, quando retornam, e pode verificar se foram chamadas corretamente. A chave é saber o que mockar e o que não mockar.

Quando substituir dependências

Três sinais de que uma dependência deve ser substituída no teste:

Rede e APIs externas: fetch é lento, pode falhar, retorna dados diferentes em execuções diferentes. Um teste que depende da rede é frágil por natureza.

Tempo: funções que leem Date.now() internamente produzem resultados diferentes dependendo de quando o teste roda. O caso ideal é injetar a data como parâmetro (lição anterior), mas quando não é possível, use mocks.

Módulos com efeitos colaterais: analytics, logging, envio de e-mails. Nos testes, você não quer que esses efeitos aconteçam de verdade.

vi.fn() — função mock individual

vi.fn() cria uma função que registra cada chamada — argumentos, número de chamadas, valor de retorno:

ts
import { describe, it, expect, vi } from "vitest";

// mock de callback
const onCurtir = vi.fn();

// passar o mock como prop e simular a interação
// onCurtir é chamado quando o botão é clicado

// verificações
expect(onCurtir).toHaveBeenCalledOnce();             // chamada exatamente uma vez
expect(onCurtir).toHaveBeenCalledWith(42);           // chamada com o argumento 42
expect(onCurtir).toHaveBeenCalledTimes(3);           // chamada três vezes
expect(onCurtir).not.toHaveBeenCalled();             // nunca chamada
vi.fn() — criar e verificar uma função mock.

Para definir o que a função retorna:

ts
// retornar um valor fixo
const buscarArtigos = vi.fn().mockReturnValue([{ id: 1, titulo: "CSS" }]);

// retornar uma Promise resolvida
const fetchArtigos = vi.fn().mockResolvedValue({ ok: true, json: async () => artigos });

// retornar uma Promise rejeitada — simular erro
const fetchComErro = vi.fn().mockRejectedValue(new Error("Rede indisponível"));

// retornar valores diferentes em cada chamada
const fetchSequencial = vi.fn()
  .mockResolvedValueOnce({ artigos: [artigo1] })   // primeira chamada
  .mockResolvedValueOnce({ artigos: [artigo2] });   // segunda chamada
vi.fn() — controlar o retorno.

vi.mock() — substituir um módulo inteiro

Quando o código importa um módulo e você quer substituí-lo nos testes:

ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { ArticleList } from "./ArticleList";
import * as api from "../api";

// substitui todas as exportações do módulo api
vi.mock("../api");

const artigosFixture = [
  { id: 1, titulo: "CSS em geral", descricao: "...", tags: ["css"], curtidas: 5 },
  { id: 2, titulo: "Flexbox",      descricao: "...", tags: ["css"], curtidas: 12 },
];

describe("ArticleList", () => {
  beforeEach(() => {
    vi.clearAllMocks(); // limpar chamadas anteriores antes de cada teste
  });

  it("exibe os artigos quando o fetch é bem-sucedido", async () => {
    // configurar o que o mock deve retornar neste teste
    vi.mocked(api.carregarArtigos).mockResolvedValue(artigosFixture);

    render(<ArticleList />);

    // aguardar os artigos aparecerem
    expect(await screen.findByText("CSS em geral")).toBeInTheDocument();
    expect(screen.getByText("Flexbox")).toBeInTheDocument();
  });

  it("exibe mensagem de erro quando o fetch falha", async () => {
    vi.mocked(api.carregarArtigos).mockRejectedValue(new Error("Erro 500"));

    render(<ArticleList />);

    expect(await screen.findByText(/Erro 500/)).toBeInTheDocument();
  });
});
vi.mock() — substituir o módulo de API.

Mockar fetch globalmente

Quando o componente usa fetch diretamente (não via módulo separado), você pode substituir o fetch global:

ts
import { vi, beforeEach } from "vitest";

beforeEach(() => {
  // substituir fetch por um mock controlado
  vi.stubGlobal("fetch", vi.fn());
});

afterEach(() => {
  vi.unstubAllGlobals(); // restaurar fetch original
});

it("carrega artigos do endpoint correto", async () => {
  const artigosMock = [{ id: 1, titulo: "CSS" }];

  // configurar o que fetch retorna
  vi.mocked(fetch).mockResolvedValue({
    ok: true,
    json: async () => artigosMock,
  } as Response);

  render(<ArticleList />);

  expect(await screen.findByText("CSS")).toBeInTheDocument();
  expect(fetch).toHaveBeenCalledWith("/artigos.json");
});
Mockar fetch global nos testes.

Mockar Date.now() para testes de tempo

Quando você não pode refatorar a função para injetar a data:

ts
import { vi, afterEach } from "vitest";

afterEach(() => {
  vi.restoreAllMocks(); // restaurar Date.now() original após cada teste
});

it("retorna 'há 5 minutos' para data de 5 minutos atrás", () => {
  const referencia = new Date("2024-06-01T12:00:00Z").getTime();
  vi.spyOn(Date, "now").mockReturnValue(referencia);

  const cincoMinutosAtras = new Date("2024-06-01T11:55:00Z");
  expect(formatarData(cincoMinutosAtras)).toBe("há 5 minutos");
});
vi.spyOn — mockar Date.now() para testes determinísticos.

Os riscos de mockar demais

Mockar em excesso é um dos problemas mais comuns em suites de teste. Quando você substitui lógica interna da sua aplicação por mocks, passa a testar o mock — não o código:

ts
// ❌ todo o comportamento real foi substituído
vi.mock("./filtrarArtigos");
vi.mock("./ordenarPorData");
vi.mock("./formatarArtigo");

it("ArticleList exibe artigos filtrados e ordenados", async () => {
  vi.mocked(filtrarArtigos).mockReturnValue([artigoMock]);
  vi.mocked(ordenarPorData).mockReturnValue([artigoMock]);
  vi.mocked(formatarArtigo).mockReturnValue(artigoFormatado);

  render(<ArticleList />);

  expect(screen.getByText(artigoFormatado.titulo)).toBeInTheDocument();
  // este teste prova que o mock funciona — não que ArticleList funciona
});
Excesso de mocks — teste que não diz nada.

A heurística correta:

  • Mockar: dependências externas ao seu sistema — rede, sistema de arquivos, APIs de terceiros, relógio do sistema, localStorage (quando não disponível no ambiente)
  • Não mockar: lógica interna da sua aplicação — filtros, formatadores, cálculos, transformações. Testar esses via mocks é testar o mock, não o código

O localStorage é um caso especial: em jsdom (o ambiente de test padrão para React), o localStorage é uma implementação real que funciona nos testes. Não é necessário mockár — só lembre de limpar com localStorage.clear() no beforeEach.

Resumo

  • Por que mockar: dependências externas são lentas, instáveis e não controláveis — rede, tempo, serviços de terceiros.
  • vi.fn(): função mock individual. Registra chamadas, controla retorno com .mockReturnValue, .mockResolvedValue, .mockRejectedValue.
  • vi.mock(módulo): substitui um módulo inteiro. Configure o retorno de cada exportação no teste com vi.mocked(fn).mockResolvedValue(...).
  • vi.spyOn: substitui um método específico de um objeto (ex: Date.now). vi.restoreAllMocks() no afterEach restaura o original.
  • Mockar demais: quando você substitui lógica interna, o teste verifica o mock — não o código. Mocks para dependências externas; código real para lógica interna.
/ checkpoint verifique seu entendimento
questão 1 de 4

Por que mockar fetch nos testes em vez de fazer requisições reais?