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

使用jest-fetch-mock模拟多API时如何消除act()包裹警告?

解决React测试中act(...)包裹状态更新的警告问题

问题背景

我在为React应用编写单元测试时遇到了麻烦:组件通过自定义hook useFetchReferenceData 调用14个静态API接口来填充各类下拉选项,测试时用jest-fetch-mock模拟了所有接口响应,测试能通过,但控制台输出大量如下警告:

Warning: An update to SearchSubmitterProfile inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state /
});
/
assert on the output */

核心代码片段

组件代码

export function SearchSubmitterProfile() {
  const [search, setSearch] = useState(true);
  const [viewResults, setViewResults] = useState(false);
  const [createSubmitterProfileState, setCreateSubmitterProfileState] = useState(INITIAL_STATE);

  useFetchReferenceData(
    createSubmitterProfileState,
    setCreateSubmitterProfileState
  );

  return (
    <div id="search-sub-profile" key="search-sub-profile" className={styles.grayBg}>
      {search ? <ConnectedSearchView /> : null}
      {viewResults ? <ConnectedSubmitterProfile /> : null}
    </div>
  );
}

export default SearchSubmitterProfile;

自定义hook核心逻辑

const { run: getLanguageIndicators } = useFetchye(`${subApiUrl}/submlanguageind`, {
  credentials: 'include',
  headers: getDefaultHeaders(),
  defer: true,
});

const buildLanguagIndDropdown = async () => {
  const currentState = { ...createSubmitterProfileState };
  const { data } = await getLanguageIndicators();
  if (data.status === 200) {
    const result = [DROPDOWN_STARTER];
    data.body.forEach(element => result.push({
      value: `${element.dialect_language_code}`,
      label: `${element.dialect_language_code} - ${element.dialect_name}`,
    }));
    currentState.staticFieldState.submitterLanguageIndicator.options = result;
    setCreateSubmitterProfileState(currentState);
  } else {
    errorList.push('Error retrieving language indicators');
  }
};

useEffect(() => {
  const fetchAllData = async () => {
    await buildRegionsDropDown();
    await buildLanguagIndDropdown();
    // ... 另外14个类似的构建函数
  };
  fetchAllData().catch(console.error);
}, []);

测试代码

describe('Search Submitter Profile reference data', () => {
  beforeEach(() => {
    enableFetchMocks();
    fetch.mockResponses(
      [JSON.stringify(mockRegions), { status: 200 }],
      [JSON.stringify(mockLanguageIndicators), { status: 200 }],
      // ... 其他14个模拟响应
    );
  });

  it('validate the search information view', async () => {
    render(
      <UserContext.Provider value={{ loggedInUser }}>
        <SearchSubmitterProfile />
      </UserContext.Provider>
    );
  });
});

解决方法

1. 用waitFor等待异步状态更新

React Testing Library的waitFor会自动把异步状态更新包裹在act(...)里,确保所有更新完成后再结束测试。修改测试代码:

import { render, waitFor, screen } from '@testing-library/react';

describe('Search Submitter Profile reference data', () => {
  beforeEach(() => {
    enableFetchMocks();
    fetch.mockResponses(
      [JSON.stringify(mockRegions), { status: 200 }],
      [JSON.stringify(mockLanguageIndicators), { status: 200 }],
      // ... 其他14个模拟响应
    );
  });

  it('validate the search information view', async () => {
    render(
      <UserContext.Provider value={{ loggedInUser }}>
        <SearchSubmitterProfile />
      </UserContext.Provider>
    );

    // 等待所有API请求和状态更新完成
    await waitFor(() => {
      // 这里可以加具体断言,比如验证某个下拉选项是否加载成功
      // 示例:expect(screen.getByText('zh-CN - 中文')).toBeInTheDocument();
    });
  });
});

2. 修复useEffect的依赖数组(可选优化)

原hook的useEffect依赖数组为空,但实际依赖createSubmitterProfileState和setCreateSubmitterProfileState,补上依赖可以避免闭包问题,让测试更稳定:

useEffect(() => {
  const fetchAllData = async () => {
    await buildRegionsDropDown();
    await buildLanguagIndDropdown();
    // ... 其他构建函数
  };
  fetchAllData().catch(console.error);
}, [createSubmitterProfileState, setCreateSubmitterProfileState]); // 添加缺失的依赖

3. 用findBy*查询方法断言异步内容

如果需要验证异步加载的UI元素,直接用findBy*系列方法(内部整合了waitFor和getBy*),自动等待状态更新:

it('validate the search information view', async () => {
  render(
    <UserContext.Provider value={{ loggedInUser }}>
      <SearchSubmitterProfile />
    </UserContext.Provider>
  );

  // 等待下拉框加载并断言选项存在
  const languageOption = await screen.findByText('zh-CN - 中文');
  expect(languageOption).toBeInTheDocument();
});

原因说明

警告的本质是:测试代码执行完毕后,异步API请求才完成并触发了组件状态更新,这些更新没有被React的act(...)包裹。waitFor和findBy*会自动处理act(...)的包裹逻辑,确保所有状态更新完成后再执行断言,彻底消除警告。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 18:06:00