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

如何用Jest测试React组件等待API响应时words数组为空?

问题:测试React组件在API请求等待期间的状态变化

组件代码

const [words, setWords] = useState(['Word','Another']);

const handleSelection = async selection => {
    setWords([]);
    const { response } = await apiCall(selection);
    setWords(response.split(' '));
};

return (
   <div>
     {words.map(word => (
         <p 
            onClick={() => handleSelection(word)} 
            data-testid='words'
         >
           {word}
         </p>
     ))}
   </div>
)

额外说明:组件其他位置有1500ms的setTimeout,通过useEffect在初始加载时执行。

当前测试代码

beforeEach(() => {
    apiCall.mockImplementation(() => ({ response: 'This is the response' }));
});


it("words disappear whilst awaiting api response", async () => {
    jest.useFakeTimers();
    const { getAllByTestId, queryAllByTestId } = render(<Comp />);

    const user = userEvent.setup({ delay: null });
    
    act(() => {
        jest.runAllTimers();
    });

    await act(async () => {
        await user.click(getAllByTestId('words')[0])
    });
    expect(getAllByTestId('words')).toHaveLength(0);
    
    jest.useRealTimers();
});

测试失败结果

Expected length: 0
Received length: 4
Received array:  [<p data-testid='words'>This</p>,<p data-testid='words'>is</p>,<p data-testid='words'>the</p>,<p data-testid='words'>response</p>]

需求:需要验证在等待API响应时,words数组会变为空状态。尝试过给apiCall的mock添加setTimeout并推进计时器到响应未返回的时间点,但未成功。


解决方案

核心问题是你的apiCall mock是同步返回结果的,导致handleSelection里的await直接跳过,setWords(response.split(' '))立刻执行,根本没捕捉到中间的空数组状态。要测试中间状态,需要让apiCall返回一个未resolved的Promise,在触发点击后先断言空状态,再手动控制Promise的resolve时机。

方法一:手动控制API Promise的resolve(推荐)

这种方式无需依赖计时器,直接控制API响应的返回时机,更灵活可靠:

it("words disappear whilst awaiting api response", async () => {
    jest.useFakeTimers();
    // 定义可手动控制的Promise,保存resolve函数
    let resolveApiCall;
    apiCall.mockImplementation(() => {
        return new Promise(resolve => {
            resolveApiCall = resolve;
        });
    });

    const { getAllByTestId, queryAllByTestId } = render(<Comp />);
    const user = userEvent.setup({ delay: null });

    // 处理初始加载的1500ms setTimeout
    act(() => {
        jest.runAllTimers();
    });
    // 验证初始状态的words数量
    expect(getAllByTestId('words')).toHaveLength(2);

    // 触发点击,注意不要用await,避免等待API返回
    user.click(getAllByTestId('words')[0]);
    
    // 等待组件更新,断言中间空状态
    await waitFor(() => {
        expect(queryAllByTestId('words')).toHaveLength(0);
    });

    // 可选:验证API响应返回后的最终状态
    resolveApiCall({ response: 'This is the response' });
    await waitFor(() => {
        expect(queryAllByTestId('words')).toHaveLength(4);
    });

    jest.useRealTimers();
});

方法二:模拟API延迟并控制计时器推进

如果需要模拟真实的API延迟场景,可以给mock添加setTimeout,然后控制计时器推进到响应未返回的时间点:

it("words disappear whilst awaiting api response", async () => {
    jest.useFakeTimers();
    // 模拟API延迟200ms返回
    apiCall.mockImplementation(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({ response: 'This is the response' });
            }, 200);
        });
    });

    const { getAllByTestId, queryAllByTestId } = render(<Comp />);
    const user = userEvent.setup({ delay: null });

    // 处理初始加载的定时器
    act(() => {
        jest.runAllTimers();
    });
    expect(getAllByTestId('words')).toHaveLength(2);

    // 触发点击
    user.click(getAllByTestId('words')[0]);
    
    // 推进计时器到API还未返回的时间点(199ms)
    act(() => {
        jest.advanceTimersByTime(199);
    });
    // 断言空状态
    await waitFor(() => {
        expect(queryAllByTestId('words')).toHaveLength(0);
    });

    // 推进到API返回的时间点,验证最终状态
    act(() => {
        jest.advanceTimersByTime(1);
    });
    await waitFor(() => {
        expect(queryAllByTestId('words')).toHaveLength(4);
    });

    jest.useRealTimers();
});

关键注意点

  • 使用React Testing Library的waitFor工具,它会自动等待组件状态更新完成,比手动嵌套act更可靠。
  • 触发点击时不要用await,否则会等待整个异步流程(包括API返回)完成,无法捕捉中间状态。

内容的提问来源于stack exchange,提问作者abbyabby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:44:57