乐闻世界logo
搜索文章和话题

Zustand Intermediate Interview Question: How to perform unit testing on Zustand store?

3月7日 11:44

Unit testing Zustand store is relatively simple because the store is a pure JavaScript object.

Basic test example:

javascript
// store.js import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), setUser: (user) => set({ user }), reset: () => set({ count: 0, user: null }) })); export default useStore;
javascript
// store.test.js import { renderHook, act } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store', () => { beforeEach(() => { // Reset store before each test useStore.setState({ count: 0, user: null }); }); test('should initialize with default values', () => { const { result } = renderHook(() => useStore()); expect(result.current.count).toBe(0); expect(result.current.user).toBeNull(); }); test('should increment count', () => { const { result } = renderHook(() => useStore()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); test('should decrement count', () => { const { result } = renderHook(() => useStore()); act(() => { result.current.decrement(); }); expect(result.current.count).toBe(-1); }); test('should set user', () => { const { result } = renderHook(() => useStore()); const mockUser = { id: 1, name: 'John' }; act(() => { result.current.setUser(mockUser); }); expect(result.current.user).toEqual(mockUser); }); test('should reset store', () => { const { result } = renderHook(() => useStore()); const mockUser = { id: 1, name: 'John' }; act(() => { result.current.setUser(mockUser); result.current.increment(); }); expect(result.current.count).toBe(1); expect(result.current.user).toEqual(mockUser); act(() => { result.current.reset(); }); expect(result.current.count).toBe(0); expect(result.current.user).toBeNull(); }); });

Testing async operations:

javascript
// store.js const useStore = create((set) => ({ user: null, loading: false, error: null, fetchUser: async (userId) => { try { set({ loading: true, error: null }); const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); set({ user: userData, loading: false }); } catch (err) { set({ error: err.message, loading: false }); } } }));
javascript
// store.test.js import { renderHook, act, waitFor } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store - Async Operations', () => { beforeEach(() => { useStore.setState({ user: null, loading: false, error: null }); }); test('should fetch user successfully', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ id: 1, name: 'John' }) }) ); const { result } = renderHook(() => useStore()); await act(async () => { await result.current.fetchUser(1); }); expect(result.current.user).toEqual({ id: 1, name: 'John' }); expect(result.current.loading).toBe(false); expect(result.current.error).toBeNull(); }); test('should handle fetch error', async () => { global.fetch = jest.fn(() => Promise.reject(new Error('Network error'))); const { result } = renderHook(() => useStore()); await act(async () => { await result.current.fetchUser(1); }); expect(result.current.error).toBe('Network error'); expect(result.current.loading).toBe(false); }); });

Testing selective subscription:

javascript
import { renderHook } from '@testing-library/react'; import useStore from './store'; describe('Zustand Store - Selective Subscription', () => { beforeEach(() => { useStore.setState({ count: 0, user: null }); }); test('should only re-render when subscribed state changes', () => { const renderCount = jest.fn(); const { result } = renderHook(() => { renderCount(); return useStore((state) => state.count); }); expect(renderCount).toHaveBeenCalledTimes(1); act(() => { useStore.getState().setUser({ id: 1, name: 'John' }); }); // Should not re-render because user changed but subscribed to count expect(renderCount).toHaveBeenCalledTimes(1); act(() => { useStore.getState().increment(); }); // Should re-render because count changed expect(renderCount).toHaveBeenCalledTimes(2); }); });

Key points:

  • Use @testing-library/react's renderHook and act for testing
  • Reset store state before each test
  • For async operations, use waitFor to wait for state updates
  • When testing selective subscription, verify re-render counts
  • Use useStore.getState() to directly access and manipulate store
标签:ReactZustand