Async thunks cause warnings in tests (wrapped in act)

huangapple go评论53阅读模式
英文:

Async thunks cause warnings in tests (wrapped in act)

问题

当尝试测试分发异步 thunk 的组件时,我收到以下警告。它们显示出来是因为测试完成后进行的更新。

这是警告可见的原因。

修复它们的一种方法是引入某种障碍,等待所有待处理的异步操作完成。但这样做会导致我的测试需要对我不想测试的逻辑进行断言。

我在这里重新创建了一个最小可复现的项目:https://github.com/karlosos/react-redux-async-warnings/tree/main/src

我的示例测试如下:

test('WHEN component rendered THEN counter value is being loaded', () => {
  // WHEN
  renderWithProviders(<App />)

  // THEN
  expect(Api.getValue).toHaveBeenCalledTimes(1);
  const loadingSpinner = screen.getByTestId('loading-spinner');
  expect(loadingSpinner).toBeInTheDocument();

  // 之后测试结束后组件会发生一些事情,确切地说,从 API 获取的数据将被显示
});

和 thunk 示例:

export const fetchCounterValue = (): AppThunk => async (dispatch, getState) => {
  if (getState().counter.fetchValueStatus === "loading") {
    return;
  }

  dispatch(fetchValueStart());
  try {
    const result = await Api.getValue();
    dispatch(fetchValueSuccess(result));
  } catch (e) {
    dispatch(fetchValueError('Could not fetch the data'));
  }
};

更新 1 - 使用 waitFor 在测试的末尾时没有警告

当我在测试的末尾添加了 await waitFor(() => new Promise(res => setTimeout(res, 0))); 时,警告就不可见了。但我不想编辑每个单独的测试用例。这似乎是一种hack。

test('WHEN component rendered THEN counter value is being loaded', async () => {
  // WHEN
  renderWithProviders(<App />)

  // THEN
  expect(Api.getValue).toHaveBeenCalledTimes(1);
  const loadingSpinner = screen.getByTestId('loading-spinner');
  expect(loadingSpinner).toBeInTheDocument();

  await waitFor(() => new Promise(res => setTimeout(res, 0)));
});

Async thunks cause warnings in tests (wrapped in act)

Async thunks cause warnings in tests (wrapped in act)

英文:

When trying to test the component which dispatches async thunk I get the following warnings. They are displayed because of updates performed after the test is finished.

  console.error
    Warning: An update to App inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() =&gt; {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you&#39;re testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at App (/home/karlosos/Dev/nokia/playground/testing-redux/src/App.tsx:6:34)
        at Provider (/home/karlosos/Dev/nokia/playground/testing-redux/node_modules/react-redux/lib/components/Provider.js:19:3)
        at Wrapper (/home/karlosos/Dev/nokia/playground/testing-redux/src/testUtils.tsx:11:22)

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
      at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
      at forceStoreRerender (node_modules/react-dom/cjs/react-dom.development.js:16977:5)
      at Object.handleStoreChange [as callback] (node_modules/react-dom/cjs/react-dom.development.js:16953:7)
      at node_modules/react-redux/lib/utils/Subscription.js:23:20

This is explanation why warnings are visible.
Async thunks cause warnings in tests (wrapped in act)

One way of fixing them is to introduce some kind of barrier that will wait for all pending async actions to be finished. But doing that my tests would need to have asserts for logic that I don't want to test.

Async thunks cause warnings in tests (wrapped in act)

I have recreated a minimal reproducible project here: https://github.com/karlosos/react-redux-async-warnings/tree/main/src

My example test looks like this:

  test(&#39;WHEN component rendered THEN counter value is being loaded&#39;, () =&gt; {
    // WHEN
    renderWithProviders(&lt;App /&gt;)

    // THEN
    expect(Api.getValue).toHaveBeenCalledTimes(1);
    const loadingSpinner = screen.getByTestId(&#39;loading-spinner&#39;);
    expect(loadingSpinner).toBeInTheDocument();

    // things will happen to the component here after test is done
    // precisely the data fetched from API will be displayed
  });

and example thunk:

export const fetchCounterValue = (): AppThunk =&gt; async (dispatch, getState) =&gt; {
  if (getState().counter.fetchValueStatus === &quot;loading&quot;) {
    return;
  }

  dispatch(fetchValueStart());
  try {
    const result = await Api.getValue();
    dispatch(fetchValueSuccess(result));
  } catch (e) {
    dispatch(fetchValueError(&#39;Could not fetch the data&#39;));
  }
};

Update 1 - No warnings when using waitFor at the end of the test

When I've added await waitFor(() =&gt; new Promise(res =&gt; setTimeout(res, 0))); at the end of the test then warnings are not visible. But I don't want to edit every single test case. It seems like a hack.

  test(&#39;WHEN component rendered THEN counter value is being loaded&#39;, async () =&gt; {
    // WHEN
    renderWithProviders(&lt;App /&gt;)

    // THEN
    expect(Api.getValue).toHaveBeenCalledTimes(1);
    const loadingSpinner = screen.getByTestId(&#39;loading-spinner&#39;);
    expect(loadingSpinner).toBeInTheDocument();

    await waitFor(() =&gt; new Promise(res =&gt; setTimeout(res, 0)));
  });

答案1

得分: 5

正确的解决方法不是设置 global.IS_REACT_ACT_ENVIRONMENT = false;,而是一开始就编写正确的测试用例。这个 act(...) 警告表示在测试完成后组件发生了一些更新。

在这种情况下,这是因为加载旋转图标消失,然后数据显示出来。为了修复这个测试场景,我们需要等待加载旋转图标消失,或者等待数据出现在屏幕上。

正确的测试用例应该如下所示:

test('WHEN component rendered THEN counter value is being loaded', async () => {
    // WHEN
    renderWithProviders(<App />)

    // THEN
    expect(Api.getValue).toHaveBeenCalledTimes(1);
    const loadingSpinner = screen.getByTestId('loading-spinner');
    expect(loadingSpinner).toBeInTheDocument();

    await waitForElementToBeRemoved(() => screen.queryByTestId('loading-spinner')); // 这可以防止 `act(...)` 警告
});

了解更多关于 act(...) 警告的信息:

英文:

The correct solution is not to set global.IS_REACT_ACT_ENVIRONMENT = false;, but to make the correct test case in the first place. This act(...) warning means that there are some updates happening to the component after the test has finished.

In this particular scenario, it is caused by the loading spinner disappearing and then data being displayed. To fix this test scenario, we need to wait for this loading spinner to disappear or alternatively wait for data to appear on the screen.

This is what a correct test case should look like:

  test(&#39;WHEN component rendered THEN counter value is being loaded&#39;, async () =&gt; {
    // WHEN
    renderWithProviders(&lt;App /&gt;)

    // THEN
    expect(Api.getValue).toHaveBeenCalledTimes(1);
    const loadingSpinner = screen.getByTestId(&#39;loading-spinner&#39;);
    expect(loadingSpinner).toBeInTheDocument();

    await waitForElementToBeRemoved(() =&gt; screen.queryByTestId(&#39;loading-spinner&#39;)); // This is preventing `act(...)` warnings
  });

Learn more about act(...) warnings here:

答案2

得分: 2

当你首次更新你的测试以使用createRoot时,你可能会在测试控制台中看到这个警告:

“当前的测试环境未配置为支持act(…)

来源:https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#configuring-your-testing-environment

英文:

> When you first update your tests to use createRoot, you may see this
> warning in your test console:
>
> The current testing environment is not configured to support act(…)

https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#configuring-your-testing-environment

huangapple
  • 本文由 发表于 2023年2月14日 21:52:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75448819.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定