首页 > 解决方案 > 如何使用两个 useEffect 语句测试钩子?

问题描述

我有以下钩子:


const useBar = () => {
  const [myFoo, setFoo] = useState(0);
  const [myBar, setBar] = useState(0);
  useEffect(() => {
    setFoo(myFoo + 1);
    console.log("setting foo (1)", myFoo, myBar);
  }, [setFoo, myFoo, myBar]);
  useEffect(() => {
    setBar(myBar + 1);
    console.log("setting bar (2)", myFoo, myBar);
  }, [setBar, myBar, myFoo]);
};

使用组件时,我有无限循环的预期行为:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Bar = () => {
  useBar();
  return <div>Bar</div>;
};

function App() {
  return (
    <Bar />
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

从 console.log :

setting foo (1) 1 1
setting bar (2) 1 1
setting foo (1) 2 2
setting bar (2) 2 2
setting foo (1) 3 3
setting bar (2) 3 3
...

但是,对此进行测试@testing-library/react-hooks仅给出第一个循环输出:

describe("Tests", () => {
  test("useBar", async () => {
    const { rerender } = renderHook(() => {
      return useBar();
    });

    // await waitForNextUpdate();
    // tried the above doesn't work

    // rerender(); 
    // with rerender it loops through the next "cycle"
  });

});

想知道如何正确测试钩子,而不调用rerender和模仿与 React 相同的行为——在状态更改时重新渲染组件。

标签: reactjsreact-testing-libraryreact-hooks-testing-library

解决方案


我猜你想测试一个在发生无限循环时hook使用useEffect并报告错误的 a 。但实际上我们无法测试无限循环,我们只能设置次数的限制。当循环超过这个限制时,我们认为它会继续无限循环。至于是否真的是无限循环,就不好证明了。

但是,要测试重复性并不容易useEffect。虽然使用waitForNextUpdate会出现超时错误,但这个错误也可能是长时间异步操作造成的,作为单元测试,需要快速反馈。您不能每次都等待超时。这是浪费时间。

感谢这个问题,我找到了方向。提供一种从测试中触发 useEffect 的方法

当我用 替换useEffect时,我可以快速准确地useLayoutEffect得到错误。Maximum update depth exceeded这只需要在所需的测试中useEffect替换为。useLayoutEffect当循环无限时,测试将失败。

编辑测试 useEffect 无限循环

import React from 'react'
import { renderHook } from '@testing-library/react-hooks'

import useBar from './useBar'

describe('Replace `useEffect` with `useLayoutEffect` to check for an infinite loop', () => {
  let useEffect = React.useEffect
  beforeAll(() => {
    React.useEffect = React.useLayoutEffect
  })
  afterAll(() => {
    React.useEffect = useEffect
  })
  it('useBar', async () => {
    const { result } = renderHook(() => useBar())
  })
})

推荐阅读