# How to Test Zustand Stores with Jest and React Testing Library: Complete Guide

> Learn to test Zustand stores with Jest and React Testing Library. Isolate tests, capture initial state, and reset stores for reliable React app development.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: how-to-guide
- Published: 2026-03-06

---

**You can test Zustand stores by creating a Jest mock that captures initial state and resets stores after each test, then using React Testing Library to verify component behavior while the mock ensures test isolation.**

Testing Zustand stores in React applications requires understanding the library's modular architecture. The `pmndrs/zustand` repository exposes a vanilla core ([`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)) and React bindings ([`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts)) that you can target with different testing strategies depending on whether you are verifying pure state logic or component integration.

## Understanding Zustand's Testing Architecture

Zustand stores are plain JavaScript objects with a minimal API surface: `getState`, `setState`, `subscribe`, and `getInitialState`. This framework-agnostic design allows you to test business logic directly or through React components.

### Core Store Implementation

The vanilla store implementation in [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) defines the fundamental behavior. When you call `createStore`, you receive an object that holds state and mutators. This layer has no React dependencies, making it ideal for pure unit tests.

### React Bindings

The [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) file exports the `useStore` hook, which internally uses `React.useSyncExternalStore` to subscribe to store slices. When testing components that consume stores via hooks like `useCounterStore`, you are exercising this binding layer.

### The Mock Pattern for Test Isolation

The recommended approach in the official documentation ([`docs/learn/guides/testing.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/testing.md)) uses a Jest/Vitest mock that wraps the real `create` function. This mock records each store's initial state in a `storeResetFns` Set and restores it after every test, preventing state bleed-through between test cases.

## Setting Up the Jest Mock for Zustand

Create a mock file at [`__mocks__/zustand.ts`](https://github.com/pmndrs/zustand/blob/main/__mocks__/zustand.ts) that intercepts store creation and registers reset functions. The mock preserves the original behavior while adding test lifecycle management.

```typescript
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'

export * from 'zustand'

const { create: actualCreate, createStore: actualCreateStore } =
  jest.requireActual<typeof ZustandExportedTypes>('zustand')

export const storeResetFns = new Set<() => void>()

const createUncurried = <T>(stateCreator: ZustandExportedTypes.StateCreator<T>) => {
  const store = actualCreate(stateCreator)
  const initialState = store.getInitialState()
  storeResetFns.add(() => store.setState(initialState, true))
  return store
}

export const create = ((stateCreator) => {
  return typeof stateCreator === 'function' ? createUncurried(stateCreator) : createUncurried
}) as typeof ZustandExportedTypes.create

```

After each test, iterate through the reset functions to restore initial state. Jest's module system automatically replaces the real `zustand` export with this shim when you configure [`jest.config.ts`](https://github.com/pmndrs/zustand/blob/main/jest.config.ts) to use the mock.

```typescript
afterEach(() => {
  act(() => {
    storeResetFns.forEach((reset) => reset())
  })
})

```

## Creating Reusable Store Logic

Extract your store definition into a shared creator function to ensure both your application and tests import identical logic. This pattern separates the state definition from the hook instantiation.

```typescript
// shared/counter-store-creator.ts
import { type StateCreator } from 'zustand'

export type CounterStore = {
  count: number
  inc: () => void
}

export const counterStoreCreator: StateCreator<CounterStore> = (set) => ({
  count: 1,
  inc: () => set((s) => ({ count: s.count + 1 })),
})

```

Then create the bound hook for your components:

```typescript
// stores/use-counter-store.ts
import { create } from 'zustand'
import { counterStoreCreator, type CounterStore } from '../shared/counter-store-creator'

export const useCounterStore = create<CounterStore>()(counterStoreCreator)

```

## Writing Tests with React Testing Library

Configure Jest with a `jsdom` environment and import testing library helpers in your setup file. The mock automatically resets store state between assertions, so each test starts fresh.

```typescript
// jest.config.ts
import type { JestConfigWithTsJest } from 'ts-jest'

const config: JestConfigWithTsJest = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['./setup-jest.ts'],
}

export default config

```

Write component tests that verify both the UI and store integration. The mock ensures the initial count returns to `1` for every test case.

```tsx
// Counter.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter } from '../../components/counter/counter'

test('increments the counter when button is clicked', async () => {
  render(<Counter />)

  expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('1')

  await userEvent.click(screen.getByRole('button', { name: /one up/i }))

  expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('2')
})

```

## Unit Testing Vanilla Stores

For logic that does not require React, test the vanilla store directly using `createStore` from `zustand/vanilla`. This approach bypasses the hook layer and tests pure state management.

```typescript
// counter-logic.test.ts
import { createStore } from 'zustand/vanilla'
import { counterStoreCreator } from './shared/counter-store-creator'

it('increments count through the store API', () => {
  const store = createStore(counterStoreCreator)
  
  expect(store.getState().count).toBe(1)
  
  store.getState().inc()
  expect(store.getState().count).toBe(2)
})

```

This pattern is particularly useful for testing complex selectors or middleware that operate on the core store object defined in [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts).

## Summary

- **Use a Jest mock** at [`__mocks__/zustand.ts`](https://github.com/pmndrs/zustand/blob/main/__mocks__/zustand.ts) to capture initial state and reset stores after each test via `storeResetFns`.
- **Separate store logic** into reusable `StateCreator` functions that can be tested independently or through React components.
- **Test React integration** with React Testing Library, relying on the mock to ensure test isolation without manual cleanup.
- **Test vanilla logic** directly by importing `createStore` from `zustand/vanilla` when you need to verify state mutations without the UI layer.
- **Reference the source** in [`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) and [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) to understand how `useSyncExternalStore` binds components to the underlying store.

## Frequently Asked Questions

### How do I prevent Zustand store state from leaking between tests?

Create a mock that records each store's `getInitialState()` when the store is first created, then call `setState(initialState, true)` in an `afterEach` hook. Wrap the reset calls in `act()` from React Testing Library to ensure proper React lifecycle handling. This pattern is documented in [`docs/learn/guides/testing.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/testing.md) and handles cleanup automatically.

### Can I test Zustand stores without React Testing Library?

Yes. Import `createStore` from `zustand/vanilla` to instantiate stores directly. Since the vanilla API exposes `getState`, `setState`, and `subscribe` methods, you can write pure unit tests using Jest assertions without rendering components. This is the approach used in [`tests/vanilla/basic.test.ts`](https://github.com/pmndrs/zustand/blob/main/tests/vanilla/basic.test.ts) within the repository.

### Why does the Jest mock use `jest.requireActual`?

The mock uses `jest.requireActual<typeof ZustandExportedTypes>('zustand')` to preserve the original implementation while adding testing instrumentation. This ensures your tests run against the real store logic while the shim captures references to reset functions. The mock maintains type safety by importing the actual module types and casting the wrapper function appropriately.

### How do I test components that use multiple Zustand stores?

The mock pattern works transparently with multiple stores. Each call to `create` registers its own reset function in the shared `storeResetFns` Set. When `afterEach` runs, it iterates through all registered stores and resets each to its initial state, ensuring complete isolation even when components consume several different stores simultaneously.