You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 17:05:19