Multi-Layer Testing
Unit tests (Node.js) + browser tests (Chromium) + E2E (Playwright) with Zustand store reset utilities.
What Problem This Solves
You want fast unit tests for logic, real browser tests for component behavior, and E2E tests for critical flows — all in the same project.
Prerequisites
vitestwith browser modeplaywrightzustand
Files
store-test-utils.ts
ts
import { type StoreApi } from "zustand";
type ResettableStore<T> = {
reset: () => void;
setState: (state: Partial<T>) => void;
getState: () => T;
};
export function createStoreResettable<T extends object>(store: StoreApi<T>): ResettableStore<T> {
const initialState = store.getState();
return {
reset: () => store.setState(initialState, true),
setState: (state) => store.setState(state, false),
getState: () => store.getState(),
};
}
export function createLocalStorageMock(): Storage {
const store: Record<string, string> = {};
return {
get length() {
return Object.keys(store).length;
},
getItem: (key) => store[key] ?? null,
setItem: (key, value) => {
store[key] = value;
},
removeItem: (key) => {
delete store[key];
},
clear: () => {
for (const key of Object.keys(store)) delete store[key];
},
key: (index) => Object.keys(store)[index] ?? null,
};
}render-with-providers.tsx
tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import type { ReactNode } from "react";
import { ThemeProvider } from "./theme-provider";
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export function renderWithProviders(ui: ReactNode) {
return render(
<QueryClientProvider client={testQueryClient}>
<ThemeProvider defaultTheme="light">{ui}</ThemeProvider>
</QueryClientProvider>,
);
}unit-setup.ts
ts
import "@testing-library/jest-dom/vitest";
// Mock localStorage for unit tests
const localStorageMock = createLocalStorageMock();
Object.defineProperty(window, "localStorage", {
value: localStorageMock,
writable: true,
});browser-setup.ts
ts
import "@testing-library/jest-dom/vitest";
// Browser tests run in real Chromium, no mocks needed
// But you may want to start MSW here if using API mockingExample: Store Unit Test
ts
import { describe, it, expect, beforeEach } from "vitest";
import { useAuthStore } from "./auth-store";
import { createStoreResettable } from "./store-test-utils";
const resettableStore = createStoreResettable(useAuthStore);
describe("AuthStore", () => {
beforeEach(() => {
resettableStore.reset();
});
it("should set session", () => {
resettableStore.setState({
token: "abc123",
isAuthenticated: true,
});
expect(resettableStore.getState().isAuthenticated).toBe(true);
});
});Example: Component Browser Test
tsx
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { renderWithProviders } from "./render-with-providers";
import { LoginForm } from "./login-form";
describe("LoginForm", () => {
it("renders email and password inputs", () => {
renderWithProviders(<LoginForm onSubmit={async () => {}} />);
expect(screen.getByLabelText("Email")).toBeInTheDocument();
expect(screen.getByLabelText("Password")).toBeInTheDocument();
});
});Example: E2E Test
ts
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "user@example.com");
await page.fill('[name="password"]', "password");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/dashboard");
});Configuration
vitest.config.ts
ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// Unit tests (Node.js, fast)
environment: "jsdom",
setupFiles: ["./tests/unit-setup.ts"],
// Browser tests (Chromium, real DOM/CSS)
browser: {
enabled: true,
provider: "playwright",
name: "chromium",
setupFiles: ["./tests/browser-setup.ts"],
},
},
});Test Strategy
| Layer | Speed | Use For | Environment |
|---|---|---|---|
| Unit | < 100ms | Store logic, pure functions, validation | Node.js + jsdom |
| Browser | ~1s | Component rendering, interactions, accessibility | Chromium |
| E2E | ~5s | Critical user flows, cross-page navigation | Playwright |
Rule of thumb: Write 70% unit tests, 20% browser tests, 10% E2E tests.