如何测试自定义Hook触发的Zustand状态变更?
解决测试React自定义Hook时Zustand getState返回undefined的问题
核心问题分析
测试useLogIn Hook引发的Zustand状态变化时,getState返回undefined,通常是以下原因导致:
- Zustand的Mock配置错误,未正确初始化测试环境的store
- 测试未提供Hook依赖的React Query上下文,导致异步逻辑无法正常执行
- 未等待React Query的异步操作完成就提前获取状态
- 组合式store的切片注册不完整
具体解决方案
1. 修复Zustand的Mock配置
避免完全Mock Zustand的核心逻辑,改用保留真实创建逻辑并支持状态重置的Mock,防止测试间状态污染:
// __mocks__/zustand.js import { act } from '@testing-library/react' import { create as actualCreate } from 'zustand' const storeResetFns = new Set() export const create = (createState) => { const store = actualCreate(createState) const initialState = store.getState() storeResetFns.add(() => store.setState(initialState, true)) return store } export const resetAllStores = () => { act(() => { storeResetFns.forEach(resetFn => resetFn()) }) }
2. 测试时正确提供上下文
使用renderHook并包裹React Query的上下文,确保Hook能正常访问依赖:
import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useLogIn } from './useLogIn' import { useAuthStore } from './store' import { resetAllStores } from '__mocks__/zustand' // 初始化React Query客户端 const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) // 上下文包装器 const wrapper = ({ children }) => ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ) // 每次测试前重置状态 beforeEach(() => resetAllStores()) test('useLogIn updates Zustand loading and data state', async () => { // 模拟登录API成功响应 vi.mock('./api', () => ({ login: vi.fn().mockResolvedValue({ token: 'test-token' }) })) // 渲染Hook const { result } = renderHook(() => useLogIn(), { wrapper }) // 触发登录操作 result.current.mutate({ username: 'test', password: 'test' }) // 检查初始加载状态 const initialState = useAuthStore.getState() expect(initialState.isLoading).toBe(true) // 等待异步操作完成,验证最终状态 await waitFor(() => { const finalState = useAuthStore.getState() expect(finalState.isLoading).toBe(false) expect(finalState.userData).toEqual({ token: 'test-token' }) }) })
3. 验证组合式Store的切片注册
确保Auth切片正确注入根Store,比如:
// store.js import { create } from 'zustand' import { authSlice } from './authSlice' export const useAuthStore = create((...args) => ({ ...authSlice(...args), // 其他切片逻辑 }))
authSlice需返回包含isLoading、userData及对应更新方法的对象:
// authSlice.js export const authSlice = (set) => ({ isLoading: false, userData: null, setIsLoading: (loading) => set({ isLoading: loading }), setUserData: (data) => set({ userData: data }) })
4. 检查自定义Hook的状态更新逻辑
确保useLogIn正确调用Zustand的状态更新方法:
// useLogIn.js import { useMutation } from '@tanstack/react-query' import { useAuthStore } from './store' import { login } from './api' export const useLogIn = () => { const setIsLoading = useAuthStore(state => state.setIsLoading) const setUserData = useAuthStore(state => state.setUserData) return useMutation({ mutationFn: credentials => login(credentials), onMutate: () => setIsLoading(true), onSuccess: data => { setUserData(data) setIsLoading(false) }, onError: () => setIsLoading(false) }) }
常见坑点
- 不要直接Mock整个Zustand store,保留真实的
create逻辑才能正确获取状态 - 若使用Zustand的
persist中间件,测试时需Mock本地存储或禁用持久化 - 必须等待React Query的异步操作完成(用
waitFor)后再检查状态
内容的提问来源于stack exchange,提问作者Sahitha Nelanga
相关产品推荐
相关产品推荐

