Jest测试中如何在测试文件内Mock Zustand Store?
解决Jest内直接Mock Zustand Store的状态更新问题
问题根源
直接在测试文件里Mock时,大多是因为模拟的Store没实现状态订阅/通知机制,或者Jest模块Mock的时机不对,导致组件无法感知状态变化,自然不会重渲染更新视图。
可行解决方案
方案1:手动实现带订阅的Mock Store
直接Mock zustand的create方法,自己维护状态和订阅逻辑,确保状态更新能触发组件重渲染:
import { render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; import { create } from 'zustand'; import MyComponent from './MyComponent'; // Mock zustand核心库 jest.mock('zustand', () => ({ create: (storeCreator) => { let state; const subscribers = new Set(); const setState = (updater) => { state = typeof updater === 'function' ? updater(state) : updater; // 通知所有订阅的组件更新 subscribers.forEach(sub => sub()); }; const getState = () => state; // 初始化状态 state = storeCreator(setState, getState, () => {}); // 返回模拟的useStore钩子 return (selector) => { const [, forceRender] = React.useState({}); React.useEffect(() => { const subscription = () => forceRender({}); subscribers.add(subscription); return () => subscribers.delete(subscription); }, []); return selector(state); }; }, })); test('点击按钮更新foo值', () => { render(<MyComponent />); // 断言初始状态 expect(screen.getByText('当前foo值:')).toHaveTextContent('当前foo值:初始值'); // 触发更新操作 fireEvent.click(screen.getByRole('button', { name: '更新foo' })); // 断言更新后的状态 expect(screen.getByText('当前foo值:')).toHaveTextContent('当前foo值:新值'); });
方案2:用zustand/testing简化Mock
如果不想手动写订阅逻辑,用官方的zustand/testing工具更省心,直接在测试文件内创建可更新的Mock Store:
import { render, screen, fireEvent } from '@testing-library/react'; import { create } from 'zustand'; import { act } from 'react-dom/test-utils'; import MyComponent from './MyComponent'; // 创建带状态更新的Mock Store const mockFooStore = create((set) => ({ foo: '初始值', foo_set: (newVal) => set({ foo: newVal }), })); // Mock zustand的create方法,返回我们的Mock Store jest.mock('zustand', () => ({ create: () => mockFooStore, })); test('点击按钮更新foo值', () => { render(<MyComponent />); expect(screen.getByText('当前foo值:')).toHaveTextContent('当前foo值:初始值'); // 点击按钮触发更新(testing-library会自动处理act) fireEvent.click(screen.getByRole('button', { name: '更新foo' })); // 或者手动调用set方法,需要用act包裹 // act(() => { // mockFooStore.getState().foo_set('新值'); // }); expect(screen.getByText('当前foo值:')).toHaveTextContent('当前foo值:新值'); });
方案3:直接Mock自定义Store文件
如果你的Store是通过自定义文件导出的(比如src/store/useFooStore.js),直接Mock这个文件比Mock zustand核心库更简单:
import { render, screen, fireEvent } from '@testing-library/react'; import { create } from 'zustand'; import MyComponent from './MyComponent'; // Mock自定义的Store模块 jest.mock('../src/store/useFooStore', () => { const mockStore = create((set) => ({ foo: '初始值', foo_set: jest.fn((newVal) => set({ foo: newVal })), })); return { default: mockStore }; }); test('点击按钮更新foo值', () => { render(<MyComponent />); fireEvent.click(screen.getByRole('button', { name: '更新foo' })); expect(screen.getByText('当前foo值:')).toHaveTextContent('当前foo值:新值'); // 还能断言方法是否被调用 expect(require('../src/store/useFooStore').default.getState().foo_set).toHaveBeenCalledWith('新值'); });
关键注意点
jest.mock会被Jest自动提升到文件顶部,确保组件导入前Mock已经生效,避免拿到原始的Store。- 所有状态更新操作如果是手动调用Store方法,要用
act包裹,确保React能正确处理状态更新的副作用。
内容的提问来源于stack exchange,提问作者noob
相关产品推荐
相关产品推荐

