Skip to content
react-global-state-hooks

Testing

Reset stores between tests, drive them from tests, and test contexts with makeProviderWrapper.

GuideTypeScriptTry an example

The problem this solves

Global stores live at module scope, so state from one test can leak into the next. Contexts are created per render, so a test needs a way to reach the store that a provider created.

Reset global stores

Create the store from a function initializer and call reset() in beforeEach. With a plain value, reset() keeps the current state; pass the values yourself (reset(state, metadata)) if you cannot change the initializer. On a persisted store, reset() also clears the stored value.

cart-store.ts
import { createContext, createGlobalState } from 'react-global-state-hooks';

// A function initializer lets reset() restore a fresh state before each test.
export const useCart = createGlobalState(() => ({ items: [] as string[] }), {
  actions: {
    add(item: string) {
      return ({ setState }) => {
        setState((state) => ({ items: [...state.items, item] }));
      };
    },
  },
});

export const CartContext = createContext(
  { items: [] as string[] },
  {
    actions: {
      add(item: string) {
        return ({ setState }) => {
          setState((state) => ({ items: [...state.items, item] }));
        };
      },
    },
  },
);

Test with a store or a context

This is the test file that runs in this site’s own test suite, shown unchanged.

cart-store.test.tsx
import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';
import { CartContext, useCart } from './cart-store';

describe('global store', () => {
  // reset() returns the store to its initial state, so tests do not leak into each other
  beforeEach(() => useCart.reset());

  it('changes state through actions, without rendering anything', () => {
    useCart.actions.add('book');

    expect(useCart.getState().items).toEqual(['book']);
  });

  it('updates a component that uses the hook', () => {
    const { result } = renderHook(() => useCart((state) => state.items.length));
    expect(result.current[0]).toBe(0);

    act(() => useCart.actions.add('book'));

    expect(result.current[0]).toBe(1);
  });
});

describe('context store', () => {
  it('gets a fresh store per render, and exposes it through makeProviderWrapper', () => {
    const { wrapper, context } = CartContext.Provider.makeProviderWrapper();

    const { result } = renderHook(() => CartContext.use((state) => state.items), { wrapper });
    expect(result.current[0]).toEqual([]);

    act(() => context.current.actions.add('book'));

    expect(result.current[0]).toEqual(['book']);
  });
});

Persistence in tests

localStorage restores when the store module loads. Set the stored value first, then import the module (with vi.resetModules() and a dynamic import() in Vitest) to test restoring and migrations.

Documentation
↑↓ Navigate ↵ Open Esc CloseRGSH docs
Get started

Shared state. Precise subscriptions.
Built-in DevTools.