RTK Query中useMutation的Jest Mock配置及useState变更测试问题
问题描述
我有一个使用RTK Query useMutation 的自定义Hook,代码如下:
const [getApiCall, { isError: apiError }] = useGetApiCallMutation();//useMutation from rtk query async function Test() { try { const response = await getApiCall.unwrap(); if (response.details) { console.log("success") setAdmin(prev => [...prev, ...existing]); } } catch (e) {} }
我编写的Jest测试代码如下:
const mockResponse = { details: [ { userUuid: '1', rank: 'Rank1', name: 'User1' }, { userUuid: '2', rank: 'Rank2', name: 'User2' } ] }; describe('unit testing', () => { jest.mock('Queries/pathToApi', () => ({ useAddAdminSupportMutation: () => [ async () => ({ unwrap: () => Promise.resolve(mockResponse) }), { isError: false } ], useGetAdminSupportsMutation: () => [ async () => ({ unwrap: () => Promise.resolve(mockResponse) }), { isError: false } ] })); const wrapper = ({ children }: any) => { return <Provider store={store}>{children}</Provider>; }; test('should call correct functions', async () => { const { result } = renderHook(() => useAddAdminSupport(true, jest.fn, 1), { wrapper }); await act(async () => { await result.current.getAllAdminSupport(); }); }); });
遇到的问题:
- 将
jest.mock放在describe块外会触发TypeError: Cannot read property 'reducerPath' of undefined; - 放在
describe块内、测试用例外则会调用真实的baseQuery API; - 想知道如何不使用MSW等第三方包正确mock
useGetApiCallMutation; - 如何测试
setAdmin这类useState的状态变更?
解决方案
一、正确Mock RTK Query的useMutation
1. 调整mock的结构与时机
RTK Query的useMutation返回的第一个函数(触发请求的函数)本身是一个Promise,且自带unwrap()方法,你之前的mock把它包装成了返回带unwrap对象的函数,不符合真实结构。同时要保证mock的API对象包含store初始化需要的属性。
正确的全局mock写法(放在文件最顶部):
const mockResponse = { details: [ { userUuid: '1', rank: 'Rank1', name: 'User1' }, { userUuid: '2', rank: 'Rank2', name: 'User2' } ] }; // 放在文件最顶部,describe之外 jest.mock('Queries/pathToApi', () => { // 模拟完整的RTK Query API结构,避免store初始化报错 const mockApi = { reducerPath: 'mockAdminApi', reducer: jest.fn(), middleware: jest.fn(), // 模拟useGetApiCallMutation useGetApiCallMutation: () => [ // 第一个元素:触发mutation的函数,返回带unwrap的Promise async () => { const promise = Promise.resolve(mockResponse); (promise as any).unwrap = () => promise; return promise; }, // 第二个元素:mutation的状态对象 { isError: false, isLoading: false, isSuccess: true } ], // 其他需要mock的mutation同理 useAddAdminSupportMutation: () => [ async () => { const promise = Promise.resolve(mockResponse); (promise as any).unwrap = () => promise; return promise; }, { isError: false, isLoading: false, isSuccess: true } ] }; return mockApi; });
2. 解决reducerPath报错问题
报错是因为真实store依赖RTK Query API的reducerPath等属性,上面的mock已经完整模拟了这些必要属性,能避免store初始化时找不到对应字段的问题。如果你的测试不需要真实store,也可以直接用一个空的mock Provider:
const wrapper = ({ children }: any) => { return <Provider store={jest.fn()}>{children}</Provider>; };
二、测试useState的状态变更(setAdmin)
要测试setAdmin的状态变化,需要让自定义Hook暴露状态或者提供追踪回调,再通过renderHook的result对象验证结果。
方法1:Hook内部暴露状态
调整自定义Hook,返回内部状态:
function useAddAdminSupport(/* 你的参数 */) { const [admin, setAdmin] = useState([]); const [getApiCall, { isError: apiError }] = useGetApiCallMutation(); async function getAllAdminSupport() { try { const response = await getApiCall().unwrap(); if (response.details) { setAdmin(prev => [...prev, ...response.details]); } } catch (e) {} } // 暴露状态和方法给测试用 return { getAllAdminSupport, admin }; }
对应的测试代码:
describe('unit testing', () => { const wrapper = ({ children }: any) => { return <Provider store={store}>{children}</Provider>; }; test('should update admin state after successful api call', async () => { const { result } = renderHook(() => useAddAdminSupport(true, jest.fn, 1), { wrapper }); // 验证初始状态 expect(result.current.admin).toEqual([]); await act(async () => { await result.current.getAllAdminSupport(); }); // 验证状态是否更新为mock数据 expect(result.current.admin).toEqual(mockResponse.details); }); });
方法2:通过回调追踪状态变更
如果不想暴露内部状态,可以给Hook传入回调参数,在setAdmin时触发:
function useAddAdminSupport(onAdminUpdate) { const [admin, setAdmin] = useState([]); const updateAdmin = (newAdmin) => { setAdmin(newAdmin); onAdminUpdate(newAdmin); }; async function getAllAdminSupport() { try { const response = await getApiCall().unwrap(); if (response.details) { updateAdmin(prev => [...prev, ...response.details]); } } catch (e) {} } return { getAllAdminSupport }; }
对应的测试代码:
test('should trigger onAdminUpdate with correct data', async () => { const mockOnAdminUpdate = jest.fn(); const { result } = renderHook(() => useAddAdminSupport(mockOnAdminUpdate), { wrapper }); await act(async () => { await result.current.getAllAdminSupport(); }); // 验证回调被调用,且参数正确 expect(mockOnAdminUpdate).toHaveBeenCalledWith(mockResponse.details); });
内容的提问来源于stack exchange,提问作者xxavierxx
相关产品推荐
相关产品推荐

