使用React Testing Library测试异步更新的React组件
异步更新的组件
最近,在一个边项目中,我们开始使用React DnD库,因为我们想要实现一个带有卡片的多容器拖放系统。
在实现功能的大部分时间后,我们决定添加一些测试以确保一切都按预期工作。在上述项目中,我们使用React Testing Library为我们的组件编写测试。
在测试拖放功能时,我们遇到了一个非常棘手的测试。这是我们的Card
组件的简化版本:
import React from 'react';
import { useDrag } from 'react-dnd';
const Card = ({
card: {
id,
title
}
}) => {
const [style, drag] = useDrag({
item: { id, type: 'card' },
collect: monitor => ({
opacity: monitor.isDragging() ? 0 : 1
})
});
return (
<li className="card" id={id} ref={drag} style={style}>
{title}
</li>
);
};
这是我们最初尝试编写的测试:
import React from 'react';
import { fireEvent } from '@testing-library/react';
import Card from './components/Card';
// 这是我们编写的一个小助手,用于连接redux和react-dnd
import renderDndConnected from './test_utils/renderDndConnected';
描述('<Card/>', () => {
let card;
beforeEach(() => {
const utils = renderDndConnected(
<Card card={{ id: '1', title: 'Card' }} />
);
card = utils.container.querySelector('.card');
});
it('初始透明度为1', () => {
expect(card.style.opacity).toEqual('1');
});
describe('当拖动开始时', () => {
beforeEach(() => {
fireEvent.dragStart(card);
});
it('透明度为0', () => {
expect(card.style.opacity).toEqual('0');
});
});
});
## 令人恐惧的`act(...)`警告
虽然测试显然没有起作用,但控制台不断地提示将测试包装在`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.
这条消息对于确定潜在问题并不是很有帮助。它唯一强调的是测试在完成后没有立即更新组件样式。测试完成后还有待处理的更新。简单来说,测试失败是因为`dragStart`事件没有立即更新`Card`组件的样式(即设置新的`opacity`)。
顺便提一下,`Card`组件与Redux连接,这可能与问题有关,但即使没有Redux,这种情况也很可能发生。这可能是因为`collect`需要一些时间来运行并向组件发送更新。
## 解决问题
深入挖掘后,我们发现除了`act()`之外,还有其他选项,例如`waitFor()`和`waitForDomChange()`。这些选项似乎更直观,因为它们的名称和编写方式更简单(使用`async await`或promise)。然而,`waitForDomChange()`在我们的情况下无法正常工作,而我们使用的`react-testing-library`版本(随`react-scripts`一起提供)已过时,并且没有导出`waitFor()`,这让我们花了半个小时才弄清楚。
在更新了`react-testing-library`之后,我们还没有准备好开始,因为控制台开始显示以下错误:
TypeError: MutationObserver is not a constructor
这需要一些搜索,最终我们找到了[这个问题](https://github.com/testing-library/react-testing-library/issues/662),它帮助我们找到了解决方案,即将`package.json`中的`test`脚本替换为以下内容:
```json [package.json]
{
// ...
"scripts": {
"test": "react-scripts test --env=jsdom-fourteen"
// ...
}
}
现在终于可以编写一个有效的测试了!如上所述,我们选择使用react-testing-library
中的waitFor()
,这实际上是对原始测试代码的唯一更改,除了上述的依赖项升级和脚本更改。在进行必要的更改后,以下是测试的代码:
```jsx import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; // 这是我们编写的一个小助手,用于连接redux和react-dnd import renderDndConnected from './test_utils/renderDndConnected'; import Card from './components/Card';
描述('
beforeEach(() => {
const utils = renderDndConnected(
it('初始透明度为1', () => { expect(card.style.opacity).toEqual('1'); });
describe('当拖动开始时', () => { beforeEach(() => { fireEvent.dragStart(card); });
it('透明度为0', async() => {
await waitFor(() => expect(card.style.opacity).toEqual('0'));
});
}); });
总结
- 有关代码导致React状态更新未包装在
act(...)
中的消息可能表明组件在测试结束后更新。 - 使用
waitFor()
可以通过使测试异步来解决此问题,但如果使用较旧版本的react-scripts
,可能需要升级react-testing-library
版本。 - 如果看到与
MutationObserver
相关的错误,可能需要更改test
脚本以将--env=jsdom-fourteen
作为参数包含进去。