英文:
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)));
});
英文:
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(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you'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.
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.
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('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();
// 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 => 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'));
}
};
Update 1 - No warnings when using waitFor
at the end of the test
When I've added await waitFor(() => new Promise(res => 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('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)));
});
答案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(...)
警告的信息:
- Fix the "not wrapped in act(...)" warning - Kent C. Dodds
- Handling React's act warnings - Philipp Fritsche
- Why should you be grateful for act() warnings in React tests - Michał Kuliński
英文:
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('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')); // 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论