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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:35:37