如何在useEffect(func,[])中使用异步api调用中的setState钩子测试组件

Posted

技术标签:

【中文标题】如何在useEffect(func,[])中使用异步api调用中的setState钩子测试组件【英文标题】:how to test component with setState hook inside async api call in useEffect(func, []) 【发布时间】:2021-11-07 16:53:45 【问题描述】:

我在使用 react-native-testing-library 对组件进行单元测试时遇到问题。

我有一个这样的组件:

// components/TestComponent.js
function TestComponent() 
  const [data, setData] = useState();

  useEffect(() => 
    clientLibrary.getData()
      .then((result) =>  setData(result.data);  )
      .catch((err) =>  //handle error here  )
  , []);

  render (
    <ListComponent 
      testID="comp" 
      data=data)
      renderItem=(item) => <ListItem testID='item' data=item />
    />
  );

我这样测试它:

// components/TestComponent.test.js

it('should render 10 list item', async () => 
  const data = new Array(10).fill().map((v, idx) => (
      id: `v_$idx`,
    ));

  const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => 
      return Promise.resolve(data);
    );

  const queryByTestId, queryAllByTestId = render(
    <TestComponent />,
  );

  expect(await queryByTestId('comp')).toBeTruthy(); // this will pass
  expect(await queryAllByTestId('item').length).toEqual(10); // this will fail with result: 0 expected: 10
); // this failed

测试将失败/通过

Attempted to log "Warning: An update to TestComponent inside a test was not wrapped in act(...). 在 useEffect 中指向 setData

我尝试使用act() 包装渲染,使用act() 包装断言,不模拟api 调用,将整个测试包装在act() 中,但错误不会消失。

我已经尝试查看测试库文档/git/q&a 来解决这个案例,也搜索了 *** 问题,但我仍然无法让这个测试正常工作。

谁能指出正确的方向来解决这个问题?

注意:我不是要测试实现细节。我只想测试给定一个获取结果 X,该组件将按预期呈现,即呈现 10 个列表项。

【问题讨论】:

您应该等待并断言使用data 在您的ListComponent 中呈现的任何内容都存在 - 这将确保您的useEffect 中的逻辑已经运行。 感谢@juliomalves 的建议。我想我应该更正我的问题中的措辞。如果我检查某些东西,测试确实通过了,但它仍然抱怨 not wrapped in act 警告指向 useEffect 中的 setState,并且由于它以红色打印,我的大脑只是认为它失败了,因为这意味着我不是做正确的事,即使它通过了。如果测试通过,忽略警告是否安全?该警告并不能完全提高我对测试的信心...... 这似乎是两个独立的问题:(1) 使用waitForfindBy 等待异步任务解决,(2) 处理act 警告。分别见how to test useEffect with act和React Native testing - act without await。 【参考方案1】:

您的组件在 useEffect 内挂载期间正在执行异步状态更新,因此渲染行为具有异步副作用,需要将其包装在 await act(async()) 调用中。请参阅testing recipes documentation on data fetching。

你可以在你的测试中尝试这样的事情:

it('should render 10 list item', async () => 
  // Get these from `screen` now instead of `render`
  const  queryByTestId, queryAllByTestId  = screen
  const data = new Array(10).fill().map((v, idx) => (
      id: `v_$idx`,
    ));

  const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => 
      return Promise.resolve(data);
    );

  await act(async () => 
    render(
      <TestComponent />
    );
  )

  expect(await queryByTestId('comp')).toBeTruthy();
  expect(await queryAllByTestId('item').length).toEqual(10);
);

【讨论】:

以上是关于如何在useEffect(func,[])中使用异步api调用中的setState钩子测试组件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UseEffect 中使用自定义方法?

如何在 Preact 中使用 useEffect?

如何在 useEffect 挂钩中使用 Promise.all 获取所有数据?

异步方法/Func 无法识别 FluentAssertions ShouldNotThrow

如何在useEffect中使用上下文值,只运行一次

如何在useEffect中使用axios请求测试反应组件?