//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Teste stores do Zustand resetando o estado entre testes, usando getState() e setState() para asserções diretas e mockando stores em testes de componentes.
// stores/counter-store.ts
import { create } from "zustand";
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
decrement: () => set((s) => ({ count: s.count - 1 })),
reset: () => set({ count: 0 }),
}));// __tests__/counter-store.test.ts
import { useCounterStore } from "@/stores/counter-store";
// Reset store before each test
beforeEach(() => {
useCounterStore.setState({ count: 0 });
});
describe("CounterStore", () => {
it("starts at zero", () => {
expect(useCounterStore.getState().count).toBe(0);
});
it("increments", () => {
useCounterStore.getState().increment();
expect(useCounterStore.getState().count).toBe(1);
});
it("decrements", () => {
useCounterStore.setState({ count: 5 });
useCounterStore.getState().decrement();
expect(useCounterStore.getState().count).toBe(4);
});
it("resets to zero", () => {
useCounterStore.setState({ count: 42 });
useCounterStore.getState().reset();
expect(useCounterStore.getState().count).toBe(0);
});
});// stores/todo-store.ts
import { create } from "zustand";
interface Todo {
id: string;
text: string;
done: boolean;
}
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
removeTodo: (id: string) => void;
getActiveTodos: () => Todo[];
getCompletedCount: () => number;
}
export const useTodoStore = create<TodoStore>((set, get) => ({
todos: [],
addTodo: (text) =>
set((s) => ({
todos: [...s.todos, { id: crypto.randomUUID(), text, done: false }],
})),
toggleTodo: (id) =>
set((s) => ({
todos: s.todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
})),
removeTodo: (id) =>
set((s) => ({ todos: s.todos.filter((t) => t.id !== id) })),
getActiveTodos: () => get().todos.filter((t) => !t.done),
getCompletedCount: () => get().todos.filter((t) => t.done).length,
}));// __tests__/todo-store.test.ts
import { useTodoStore } from "@/stores/todo-store";
import { act } from "@testing-library/react";
const initialState = useTodoStore.getState();
beforeEach(() => {
useTodoStore.setState(initialState, true); // true = replace
});
describe("TodoStore", () => {
it("adds a todo", () => {
act(() => {
useTodoStore.getState().addTodo("Buy milk");
});
const { todos } = useTodoStore.getState();
expect(todos).toHaveLength(1);
expect(todos[0].text).toBe("Buy milk");
expect(todos[0].done).toBe(false);
});
it("toggles a todo", () => {
// Seed state
useTodoStore.setState({
todos: [{ id: "1", text: "Test", done: false }],
});
act(() => {
useTodoStore.getState().toggleTodo("1");
});
expect(useTodoStore.getState().todos[0].done).toBe(true);
});
it("removes a todo", () => {
useTodoStore.setState({
todos: [
{ id: "1", text: "A", done: false },
{ id: "2", text: "B", done: true },
],
});
act(() => {
useTodoStore.getState().removeTodo("1");
});
expect(useTodoStore.getState().todos).toHaveLength(1);
expect(useTodoStore.getState().todos[0].id).toBe("2");
});
it("computes active todos", () => {
useTodoStore.setState({
todos: [
{ id: "1", text: "A", done: false },
{ id: "2", text: "B", done: true },
{ id: "3", text: "C", done: false },
],
});
expect(useTodoStore.getState().getActiveTodos()).toHaveLength(2);
expect(useTodoStore.getState().getCompletedCount()).toBe(1);
});
});// __tests__/todo-component.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { useTodoStore } from "@/stores/todo-store";
import { TodoList } from "@/components/todo-list";
// Reset before each test
beforeEach(() => {
useTodoStore.setState({ todos: [] }, true);
});
describe("TodoList component", () => {
it("renders todos from the store", () => {
useTodoStore.setState({
todos: [
{ id: "1", text: "Write tests", done: false },
{ id: "2", text: "Ship feature", done: true },
],
});
render(<TodoList />);
expect(screen.getByText("Write tests")).toBeInTheDocument();
expect(screen.getByText("Ship feature")).toBeInTheDocument();
});
it("adds a todo via the form", async () => {
render(<TodoList />);
const input = screen.getByRole("textbox");
const button = screen.getByRole("button", { name: /add/i });
fireEvent.change(input, { target: { value: "New todo" } });
fireEvent.click(button);
expect(useTodoStore.getState().todos).toHaveLength(1);
expect(useTodoStore.getState().todos[0].text).toBe("New todo");
});
});getState() e setState() no próprio hook, permitindo inspeção e manipulação direta do estado sem renderizar componentes.setState(state, replace) com replace: true substitui o estado inteiro em vez de mesclar. Isso é essencial para resets limpos de testes.getState(). Chame-as diretamente nos testes.setState antes de renderizar.Utilitário de reset global:
// test-utils/reset-stores.ts
import { useCounterStore } from "@/stores/counter-store";
import { useTodoStore } from "@/stores/todo-store";
const stores = [
{ store: useCounterStore, initial: { count: 0 } },
{ store: useTodoStore, initial: { todos: [] } },
];
export function resetAllStores() {
stores.forEach(({ store, initial }) => {
(store as any).setState(initial, true);
});
}
// In setupTests.ts
beforeEach(() => resetAllStores());Mockando um store inteiramente:
// __tests__/header.test.tsx
import { vi } from "vitest";
vi.mock("@/stores/auth-store", () => ({
useAuthStore: vi.fn((selector) =>
selector({
user: { name: "Test User", role: "admin" },
token: "fake-token",
isAuthenticated: () => true,
logout: vi.fn(),
})
),
}));
import { render, screen } from "@testing-library/react";
import { Header } from "@/components/header";
it("shows admin panel link for admin users", () => {
render(<Header />);
expect(screen.getByText("Admin Panel")).toBeInTheDocument();
});Testando ações assíncronas:
import { useTodoStore } from "@/stores/todo-store";
// Mock fetch
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: "1", text: "From API", done: false }]),
} as Response)
);
it("fetches todos from API", async () => {
await useTodoStore.getState().fetchTodos();
expect(fetch).toHaveBeenCalledWith("/api/todos");
expect(useTodoStore.getState().todos).toHaveLength(1);
expect(useTodoStore.getState().todos[0].text).toBe("From API");
});Testando subscriptions:
it("calls subscriber on state change", () => {
const listener = vi.fn();
const unsub = useCounterStore.subscribe(listener);
useCounterStore.getState().increment();
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({ count: 1 }),
expect.objectContaining({ count: 0 })
);
unsub();
});getState() retorna o estado completo tipado, incluindo ações.setState aceita Partial<State> ou (s: State) => Partial<State>.// Type-safe mock
const mockState: ReturnType<typeof useAuthStore.getState> = {
user: { name: "Test", email: "test@test.com", role: "admin" },
token: "fake",
login: vi.fn(),
logout: vi.fn(),
isAuthenticated: () => true,
};setState({}, true) com replace: true remove todas as propriedades, incluindo ações. Passe o estado inicial completo, incluindo referências de ações, ou apenas resete as propriedades de dados.vi.mock é a nível de módulo e se aplica a todos os testes no arquivo. Use vi.fn().mockReturnValue() por teste para diferentes estados de mock.act() de @testing-library/react pode ser necessário quando mudanças no store disparam atualizações de estado do React em componentes renderizados.await nos testes. Use await store.getState().asyncAction() ou waitFor da testing-library.persist, os testes podem tentar acessar o localStorage. Faça um mock dele ou use um adaptador de armazenamento em memória nos testes.| Abordagem | Prós | Contras |
|---|---|---|
getState/setState direto | Não precisa de renderização, testes unitários rápidos | Não testa a integração com componentes |
| Testes de componentes com store real | Testa a integração completa | Mais lento, mais configuração |
Store mockado (vi.mock) | Isola o componente da lógica do store | Mock pode divergir do store real |
createStore por teste | Isolamento completo, sem necessidade de reset | Mais boilerplate |
beforeEach(() => {
useCounterStore.setState({ count: 0 });
});setState com o estado inicial antes de cada teste.setState(initialState, true) com replace: true para um reset completo.it("increments", () => {
useCounterStore.getState().increment();
expect(useCounterStore.getState().count).toBe(1);
});getState() para chamar ações e ler o estado diretamente.setState antes de render() para popular o store com dados de teste.vi.mock("@/stores/auth-store", () => ({
useAuthStore: vi.fn((selector) =>
selector({
user: { name: "Test User" },
logout: vi.fn(),
})
),
}));vi.mock no nível do módulo. O mock se aplica a todos os testes no arquivo.replace: true.replace para manter as ações intactas.act() de @testing-library/react.act() nem sempre é necessário.it("fetches todos", async () => {
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: "1", text: "Test" }]),
} as Response)
);
await useTodoStore.getState().fetchTodos();
expect(useTodoStore.getState().todos).toHaveLength(1);
});fetch, então use await na ação assíncrona diretamente.it("calls subscriber on change", () => {
const listener = vi.fn();
const unsub = useCounterStore.subscribe(listener);
useCounterStore.getState().increment();
expect(listener).toHaveBeenCalledTimes(1);
unsub();
});const mockState: ReturnType<typeof useAuthStore.getState> = {
user: { name: "Test", email: "t@t.com", role: "admin" },
token: "fake",
login: vi.fn(),
logout: vi.fn(),
isAuthenticated: () => true,
};ReturnType<typeof store.getState> para um mock type-safe.localStorage for acessado.localStorage ou use um adaptador de armazenamento em memória na configuração do seu teste.Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥