Mocking em Testes
Faça mock de módulos, funções, APIs e internos do Next.js para isolar o código sob teste.
Busque em todas as páginas da documentação
Faça mock de módulos, funções, APIs e internos do Next.js para isolar o código sob teste.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida -- pronto para copiar e colar.
import { vi } from "vitest";
// Mock de um módulo
vi.mock("@/lib/analytics", () => ({
trackEvent: vi.fn(),
}));
// Mock de uma função
const onSubmit = vi.fn();
onSubmit.mockResolvedValueOnce({ success: true });
// Mock de fetch
vi.stubGlobal("fetch", vi.fn());
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify({ data: [] }), { status: 200 })
);
// Mock de next/navigation
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), back: vi.fn(), refresh: vi.fn() }),
usePathname: () => "/dashboard",
useSearchParams: () => new URLSearchParams("?tab=settings"),
}));
// Mock de next/image
vi.mock("next/image", () => ({
default: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (
<img {...props} />
),
}));Quando usar isso: Quando seu componente depende de módulos externos, chamadas de API ou internos do Next.js que você precisa controlar em testes.
// src/components/user-profile.tsx
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
interface User {
id: number;
name: string;
email: string;
}
export function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
async function loadUser() {
try {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error("Failed to load user");
setUser(await res.json());
} catch (err) {
setError(err instanceof Error ? err.message : "Unknown error");
} finally {
setLoading(false);
}
}
loadUser();
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p role="alert">{error}</p>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={() => router.push(`/users/${userId}/edit`)}>
Edit Profile
</button>
</div>
);
}// src/components/user-profile.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, it, expect, beforeEach } from "vitest";
import { UserProfile } from "./user-profile";
// Mock de next/navigation
const mockPush = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({
push: mockPush,
back: vi.fn(),
refresh: vi.fn(),
}),
usePathname: () => "/users/1",
useSearchParams: () => new URLSearchParams(),
}));
describe("UserProfile", () => {
const user = userEvent.setup();
beforeEach(() => {
vi.stubGlobal("fetch", vi.fn());
mockPush.mockClear();
});
it("shows loading state initially", () => {
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify({ id: 1, name: "Alice", email: "alice@test.com" }))
);
render(<UserProfile userId={1} />);
expect(screen.getByText("Loading...")).toBeInTheDocument();
});
it("renders user data on success", async () => {
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify({ id: 1, name: "Alice", email: "alice@test.com" }))
);
render(<UserProfile userId={1} />);
expect(await screen.findByText("Alice")).toBeInTheDocument();
expect(screen.getByText("alice@test.com")).toBeInTheDocument();
});
it("shows error on fetch failure", async () => {
vi.mocked(fetch).mockResolvedValue(new Response(null, { status: 500 }));
render(<UserProfile userId={1} />);
expect(await screen.findByRole("alert")).toHaveTextContent(
"Failed to load user"
);
});
it("navigates to edit page on button click", async () => {
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify({ id: 1, name: "Alice", email: "alice@test.com" }))
);
render(<UserProfile userId={1} />);
await screen.findByText("Alice");
await user.click(screen.getByRole("button", { name: /edit profile/i }));
expect(mockPush).toHaveBeenCalledWith("/users/1/edit");
});
});O que isso demonstra:
next/navigationfetch com vi.stubGlobalmockPush para asserçãovi.mock() substitui um módulo inteiro no topo do arquivo -- ele é içado acima das importações pela transformação do Vitestvi.fn() cria uma função mock que rastreia chamadas, argumentos e valores de retornovi.mocked() é um helper de tipo que converte uma função para seu tipo mockado, permitindo autocompletar para métodos mockadosvi.stubGlobal() substitui um global como fetch e o restaura quando você chama vi.restoreAllMocks()vi.resetModules() entre os testesMSW (Mock Service Worker) para mocking de API:
npm install -D msw// src/mocks/handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users/:id", ({ params }) => {
return HttpResponse.json({
id: Number(params.id),
name: "Alice",
email: "alice@test.com",
});
}),
http.post("/api/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 99, ...body }, { status: 201 });
}),
http.get("/api/users/:id", () => {
return HttpResponse.json(
{ message: "Not found" },
{ status: 404 }
);
}),
];// src/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);// vitest.setup.ts
import { server } from "./src/mocks/server";
import { beforeAll, afterAll, afterEach } from "vitest";
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());// Teste usando MSW -- nenhum mock de fetch manual necessário
import { http, HttpResponse } from "msw";
import { server } from "@/mocks/server";
it("handles 404", async () => {
// Sobrescrever para este teste específico
server.use(
http.get("/api/users/:id", () => {
return HttpResponse.json({ message: "Not found" }, { status: 404 });
})
);
render(<UserProfile userId={999} />);
expect(await screen.findByRole("alert")).toBeInTheDocument();
});Mocking com Jest (mesmos padrões, API diferente):
// Equivalente a jest.mock
jest.mock("next/navigation", () => ({
useRouter: () => ({ push: jest.fn() }),
usePathname: () => "/dashboard",
useSearchParams: () => new URLSearchParams(),
}));
// Equivalente a jest.fn
const handleSubmit = jest.fn();
handleSubmit.mockResolvedValue({ success: true });Espionar um método sem mockar completamente:
import * as analytics from "@/lib/analytics";
it("tracks page view", () => {
const spy = vi.spyOn(analytics, "trackEvent");
render(<Dashboard />);
expect(spy).toHaveBeenCalledWith("page_view", { page: "dashboard" });
spy.mockRestore();
});// vi.mocked fornece segurança de tipo completa
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify(data)) // TypeScript conhece a assinatura de fetch
);
// Tipando implementações de mock
vi.mock("@/lib/db", () => ({
getUser: vi.fn<[number], Promise<User>>(),
}));
// Asserindo chamadas de mock com tipos
const mockFn = vi.fn<[string, number], boolean>();
mockFn("hello", 42);
expect(mockFn).toHaveBeenCalledWith("hello", 42);vi.mock não içado corretamente -- Vitest iça vi.mock() para o topo do arquivo, mas variáveis definidas antes dele não estão disponíveis dentro da fábrica. Correção: Use vi.hoisted() para declarar variáveis que precisam estar disponíveis dentro de vi.mock():
const \{ mockPush \} = vi.hoisted(() => (\{
mockPush: vi.fn(),
\}));
vi.mock("next/navigation", () => (\{
useRouter: () => (\{ push: mockPush \}),
\}));Mockando demais -- Mockar cada dependência faz com que os testes passem mesmo quando as integrações estão quebradas. Correção: Use MSW para mocking de API (testa código fetch real) e apenas mock o que é verdadeiramente externo.
Estado do mock vazando entre testes -- Valores de retorno do mock persistem entre testes no mesmo arquivo. Correção: Chame vi.clearAllMocks() ou vi.restoreAllMocks() em beforeEach.
vi.mocked em funções não mockadas -- vi.mocked(fetch) falha em tempo de execução se fetch não foi substituído por vi.fn(). Correção: Sempre vi.stubGlobal("fetch", vi.fn()) antes de usar vi.mocked(fetch).
MSW interceptando todas as requisições -- onUnhandledRequest: "error" lança um erro para requisições não correspondidas. Correção: Forneça handlers para todas as requisições que seus testes fazem, ou use "warn" durante o desenvolvimento.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| MSW | Você quer mocking de API em nível de rede que funciona em testes e Storybook | Você precisa mockar módulos não HTTP |
vi.mock / jest.mock | Você precisa substituir internos de módulos (hooks, funções utilitárias) | Você só precisa mockar chamadas HTTP (prefira MSW) |
vi.spyOn / jest.spyOn | Você quer observar chamadas sem alterar o comportamento | Você precisa substituir completamente um módulo |
| Injeção de dependência | A arquitetura suporta passar dependências como props ou configuração | Você precisaria refatorar pesadamente |
vi.mock() substitui um módulo inteiro no topo do arquivo.vi.fn() cria uma função mock independente que rastreia chamadas.vi.spyOn() envolve um método existente para observar chamadas sem substituir completamente o módulo.const mockPush = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush, back: vi.fn() }),
usePathname: () => "/dashboard",
useSearchParams: () => new URLSearchParams(),
}));vi.mock() é içado acima das importações, então variáveis declaradas antes dele não estão disponíveis dentro da fábrica. Use vi.hoisted():
const { mockPush } = vi.hoisted(() => ({
mockPush: vi.fn(),
}));
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush }),
}));vi.stubGlobal("fetch") para mocking rápido e pontual.Chame vi.clearAllMocks() ou vi.restoreAllMocks() em beforeEach. Isso redefine o histórico de chamadas e os valores de retorno entre os testes.
vi.mock("next/image", () => ({
default: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (
<img {...props} />
),
}));vi.mocked() é apenas um helper de tipo. Se fetch não foi substituído por vi.fn() via vi.stubGlobal("fetch", vi.fn()), chamar métodos mockados nele falhará.
const mockFn = vi.fn<[string, number], boolean>();
vi.mock("@/lib/db", () => ({
getUser: vi.fn<[number], Promise<User>>(),
}));src/mocks/handlers.ts usando http.get(), http.post(), etc.src/mocks/server.ts com setupServer(...handlers).vitest.setup.ts, chame server.listen() em beforeAll, server.resetHandlers() em afterEach, e server.close() em afterAll.Ele lança um erro para qualquer requisição fetch que não corresponda a um handler definido. Isso garante que todas as chamadas de API em seus testes sejam explicitamente tratadas. Use "warn" durante o desenvolvimento se isso for muito rigoroso.
const spy = vi.spyOn(analytics, "trackEvent");
render(<Dashboard />);
expect(spy).toHaveBeenCalledWith("page_view", { page: "dashboard" });
spy.mockRestore();jest.mock() funciona da mesma maneira. Substitua vi.fn() por jest.fn() e vi.mocked() por jest.mocked(). Os padrões são idênticos.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥