React组件在状态更改时不会在Jest下重新渲染



组件:

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;
};

Jest测试:

it('should ...', async () => {
render(
<MyComponent price={300}/>)
)
await waitFor(() => {
expect(...).toBeInTheDocument();
});
});

上面的代码确实发生了MyComponent在运行测试时,只渲染一次,而不是两次(当应用程序运行时)。在result1是空数组的初始渲染之后,useResultuseEffect正在运行,并且由于setResult(data)导致状态发生变化,因此我应该期望MyComponent被重新渲染。然而,事实并非如此,result1仍然等于[],而它应该等于[{price:300}]

因此,测试中的自定义挂钩的行为似乎与实际应用程序不同。我认为可以通过调用它们的组件来间接测试它们。

对上述有任何解释/想法吗?

更新

引发上述错误行为的问题是状态突变!!它适用于应用程序,但不适用于测试!我的错误是试图使用push向作为状态变量的数组中添加元素

好吧,您似乎在问一个关于测试自定义钩子的非常具体的问题。在这种情况下,我在过去通过@testing-library测试自定义挂钩时也遇到了一些问题,并且创建了一个不同的包(最近合并到@testing-library中),该包提供了用于测试自定义挂钩的renderHook()功能。我建议你测试一下。

  • 原始包(不要使用它。直接使用TL包)
  • TL文档中有关renderHook()调用的文档

你可以在Kent C.Dods的这篇博客文章中阅读更多关于它的信息。

我还建议您创建一个";状态改变";以测试您的组件并使用renderHook()测试挂钩。

这里有一个简单的代码沙盒,其中包含一些与您的案例类似的组件测试。

原始答案

从本质上讲,您的测试并不是在等待组件执行副作用。有两种方法可以等待:

  • 使用waitFor()
import { waitFor, screen } from '@testing-library/react'
// ...
// add the `async` before the callback function
it('should ...', async () => {
render(<MyComponent price={300}/>);

await waitFor(() =>
expect(screen.getByText('your-text-goes-here')).toBeInTheDocument()
)
});
  • 使用RTL中的findBy*查询,返回Promise(在此处读取文档),并且是waitForgetBy*查询的组合(在此处阅读文档)
import { screen } from '@testing-library/react'
// ...
// add the `async` before the callback function
it('should ...', async () => {
render(<MyComponent price={300}/>);
expect(await screen.findByText('your-text-goes-here')).toBeInTheDocument();
});

步骤1:正在测试的代码

如果如问题评论中所述,效果内部的操作是同步的,则在所有情况下都不希望使用useEffect基于道具设置此状态。不仅仅是为了测试。

该组件将渲染、更新DOM,并立即需要重新渲染以下帧,因为它的状态已更新。它会给用户带来闪光效果,并不必要地降低应用程序的速度。

如果操作很便宜,那么只在每次渲染时执行它会更高效。

如果操作可能更昂贵,您可以将其封装在useMemo中,以确保它只在输入发生更改时发生。

export const useResult = (price) => {
return useMemo(
// I assume this is a stub for a expensive operation.
() => [{price: price}],
[price]
);
};

如果出于某种不明确的原因,您确实需要在效果上这样做(您可能不需要,但存在边缘情况),则可以使用layoutEffect。它将被同步处理,并避免闪烁的帧仍然不建议使用,但与常规效果相比略有改善。

第2步:测试

如果您将组件更改为不使用效果,那么它从第一次渲染开始就应该是正确的,并且您不再有问题了。从一开始就避免出现问题也是一个有效的解决方案:D

如果您确实发现需要在测试中同步刷新某些内容,那么现在有flushSync函数可以做到这一点。

也许它也会刷新状态更新,使您的测试在没有其他更改的情况下工作。我想应该是这样,因为刷新时效果触发的新更新应该在返回之前继续处理。

flushSync(() => {
render(
<MyComponent price={300}/>)
)
})

在任何情况下,如果可以改进组件以修复通过在效果中设置状态而引入的附加渲染,那么这样做都没有意义。

你可以做:

The test will have to be async: it('should ...',  async() => { ....
await screen.findByText('whatever');
This is async so it will wait to find whatever and fail if it can't find it
or you can do
await waitFor (() => {
const whatever = screen.getByText('whatever');
expect(whatever).toBeInTheDocument();
})

您没有等待组件被重新提交

import { waitFor, screen } from 'testing-library/react'
it('should ...',  async () => {
render(
<MyComponent price={300}/>)
)

await waitFor (() => {
// check that props.price is shown
screen.debug() // check what's renderered
expect(screen.getByText(300)).toBeInTheDocument();
});
});

相关内容

  • 没有找到相关文章

最新更新