Skip to main content
This guide covers setting up a testing environment for Zustand, including test runners, UI testing tools, and practical examples.

Setting Up a Test Environment

Test Runners

Your test runner needs to be configured to run JavaScript/TypeScript syntax. For UI components, configure the test runner to use JSDOM for a mock DOM environment.

Jest

Industry-standard testing framework with great ecosystem support

Vitest

Fast, modern testing framework with ES modules support
Configuration Resources:

UI and Network Testing Tools

We recommend using React Testing Library (RTL) to test React components that connect to Zustand. RTL encourages good testing practices and uses ReactDOM’s render and act utilities.
Additional Tools:

Setting Up Zustand for Testing

Jest uses CommonJS modules while Vitest uses ES modules. Keep this in mind when setting up your tests.
The mock below enables test runners to reset Zustand stores after each test.
1

Create Zustand Mock

__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')

// a variable to hold reset functions for all stores declared in the app
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
}

// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = (<T>(
  stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
  console.log('zustand create mock')

  // to support curried version of create
  return typeof stateCreator === 'function'
    ? createUncurried(stateCreator)
    : createUncurried
}) as typeof ZustandExportedTypes.create

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

// when creating a store, we get its initial state, create a reset function and add it in the set
export const createStore = (<T>(
  stateCreator: ZustandExportedTypes.StateCreator<T>,
) => {
  console.log('zustand createStore mock')

  // to support curried version of createStore
  return typeof stateCreator === 'function'
    ? createStoreUncurried(stateCreator)
    : createStoreUncurried
}) as typeof ZustandExportedTypes.createStore

// reset all stores after each test run
afterEach(() => {
  act(() => {
    storeResetFns.forEach((resetFn) => {
      resetFn()
    })
  })
})
2

Setup Test Environment

setup-jest.ts
import '@testing-library/jest-dom'
3

Configure Jest

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

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

export default config
To use TypeScript, install ts-jest and ts-node.

Testing Components

Let’s create a shared counter store for our examples:
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((state) => ({ count: state.count + 1 })),
})
1

Create Store

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

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

Create Component

components/counter/counter.tsx
import { useCounterStore } from '../../stores/use-counter-store'

export function Counter() {
  const { count, inc } = useCounterStore()

  return (
    <div>
      <h2>Counter Store</h2>
      <h4>{count}</h4>
      <button onClick={inc}>One Up</button>
    </div>
  )
}
3

Write Tests

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

describe('Counter', () => {
  test('should render with initial state of 1', async () => {
    render(<Counter />)

    expect(await screen.findByText(/^1$/)).toBeInTheDocument()
    expect(
      await screen.findByRole('button', { name: /one up/i }),
    ).toBeInTheDocument()
  })

  test('should increase count by clicking a button', async () => {
    const user = userEvent.setup()
    render(<Counter />)

    expect(await screen.findByText(/^1$/)).toBeInTheDocument()

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

    expect(await screen.findByText(/^2$/)).toBeInTheDocument()
  })
})
Without globals configuration in Vitest, add import { describe, test, expect } from 'vitest' at the top of each test file.

Testing Stores Directly

You can also test stores directly using getState() instead of rendering components:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter, useCounterStore } from '../../../stores/use-counter-store.ts'

describe('Counter Store', () => {
  test('should have initial state of 1', () => {
    expect(useCounterStore.getState().count).toBe(1)
  })

  test('should increase count', async () => {
    const user = userEvent.setup()
    render(<Counter />)

    expect(useCounterStore.getState().count).toBe(1)

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

    expect(useCounterStore.getState().count).toBe(2)
  })
})

References

Live Demos

Jest Demo

Complete working example with Jest

Vitest Demo

Complete working example with Vitest

Build docs developers (and LLMs) love