组成部分:
const MyComponent = props => {
const {price} = props;
const result1 = useResult(price);
return (
<div>...</div>
)
}
自定义挂钩:
export const useResult = (price) => {
const [result, setResult] = useState([]);
useEffect(() => {
const data = [{price: price}]
setResult(data);
}, [price]);
return result;
};
玩笑测试:
it('should ...', async () => {
render(
<MyComponent price={300}/>)
)
await waitFor(() => {
expect(...).toBeInTheDocument();
});
});
上面的代码所发生的情况是MyComponent
,在运行测试时,只呈现一次而不是两次(当应用程序运行时)。在result1
为空数组的初始渲染之后,useResult
的useEffect
正在运行,并且由于存在由于setResult(data)
而导致的状态更改,因此我应该期望重新渲染MyComponent
。然而,事实并非如此,result1
仍然等于[]
,而它应该等于[{price:300}]
.
因此,* 在测试中的自定义钩子的行为似乎与真实的的应用程序不同 *。我认为通过调用它们的组件间接测试它们是可以的。
***对上述***有何解释/想法?
更新
- 调用上述错误行为的问题是状态突变!!它在应用程序中工作,但在测试中不工作!我的错误是试图使用
push
向状态变量数组中添加元素... *
4条答案
按热度按时间8hhllhi21#
好吧,看起来你问的是一个关于测试自定义钩子的非常具体的问题。在这种情况下,我在过去通过
@testing-library
测试自定义钩子时也遇到了一些问题,并且创建了一个不同的包(最近被合并到@testing-library
中),它提供了renderHook()
函数来测试自定义钩子。我建议你测试它。renderHook()
调用的文档您可以在Kent C. Dodds的这篇博客文章中阅读更多关于它的信息。
我还建议您创建一个“状态更改”来测试组件,并使用
renderHook()
测试钩子。此处is a simple codesandbox with some tests表示与您的情况类似的组件。
原始答案
从本质上讲,你的测试不是等待组件执行副作用,有两种等待的方式:
waitFor()
findBy*
查询,该查询返回Promise(在此处阅读文档),并且是waitFor
和getBy*
查询的组合(在此处阅读文档)gdrx4gfi2#
Step 1: the code being tested
If, as mentioned in the comments of the question, the operation inside the effect is synchronous, then using
useEffect
for setting this state based on the props is undesirable in all cases. Not only for testing.The component will render, update the DOM and immediately need to re render the following frame because it's state was updated. It causes a flash effect for the user and needlessly slows the app down.
If the operation is cheap, it's way more efficient to just execute it on every render.
If the operation can be more expensive, you can wrap it in
useMemo
to ensure it only happens when there's changes to the inputs.If, for some obscure reason, you do need to do this in an effect anyway (you probably don't but there's edge cases), you can use a
layoutEffect
instead. It will be processed synchronously and avoid the flashing frame. Still wouldn't recommend it but it's a slight improvement over a regular effect.Step 2: Testing
If you changed the component to not use an effect, it should now be correct from the first render, and you don't have the problem anymore. Avoiding having a problem in the first place is also a valid solution :D
If you do find the need to flush something synchronously in a test, there's now the
flushSync
function which does just that.Perhaps it would also flush the state update in the effect, causing your test to work with no other changes. I guess it should, as new updates triggered by effects while flushing should continue to be processed before returning.
In any case there's no point doing this if you can instead improve the component to fix the additional render introduced by setting state in an effect.
0vvn1miw3#
您可以:
6mw9ycah4#
您没有等待组件重新呈现