开玩笑:setTimeout被调用太多了

时间:2019-04-22 01:16:02

标签: reactjs unit-testing timer jestjs react-hooks

我正在测试使用react的{​​{1}}组件。 问题是setTimeout表示Jest被调用,即使它显然没有被调用。有一个setTimeout可以从ui中删除某些内容,而另一个当鼠标悬停在组件上时,可以暂停计时器。

我尝试在setTimeout所在的位置添加一个console.log(),并且从未调用控制台日志,这意味着未调用应用程序中的setTimeout。

setTimeout
//app
const App = (props) => {
  const [show, setShow] = useState(true);
  const date = useRef(Date.now());
  const remaining = useRef(props.duration);

  let timeout;
  useEffect(() => {
    console.log('Should not run');
    if (props.duration) {
      timeout = setTimeout(() => {
        setShow(false)
      }, props.duration);
    }
  }, [props.duration]);

  const pause = () => {
    remaining.current -= Date.now() - date.current;
    clearTimeout(timeout);
  }

  const play = () => {
    date.current = Date.now();
    clearTimeout(timeout);
    console.log('should not run');
    timeout = setTimeout(() => {
      setIn(false);
    }, remaining.current);
  }

  return (
    <div onMouseOver={pause} onMouseLeave={play}>
      { show &&
        props.content
      }
    </div>
  )
}

因此,在第一次测试中,不应该调用//test it('Should not setTimeout when duration is false', () => { render(<Toast content="" duration={false} />); //setTimeout is called once but does not come from App expect(setTimeout).toHaveBeenCalledTimes(0); }); it('Should pause the timer when pauseOnHover is true', () => { const { container } = render(<Toast content="" pauseOnHover={true} />); fireEvent.mouseOver(container.firstChild); expect(clearTimeout).toHaveBeenCalledTimes(1); fireEvent.mouseLeave(container.firstChild); //setTimeout is called 3 times but does not come from App expect(setTimeout).toHaveBeenCalledTimes(1); }); ,但我收到一次调用。在第二个测试中,setTimeout应该被调用一次,但是被调用3次。该应用正常运行,我只是不了解setTimeout的运行情况,提示{{1} }的呼声更高。

2 个答案:

答案 0 :(得分:1)

我在Jest测试的第一个测试中遇到了完全相同的问题,总是调用一次setTimeout(没有我的组件触发它)。通过记录此“未知” setTimeout调用的参数,我发现它是通过_flushCallback函数和延迟0调用的。

查看react-test-renderer的存储库显示here已定义一个_flushCallback函数。 Scheduler所在的_flushCallback明确指出,它在非DOM环境中运行时会使用setTimeout(在进行Jest测试时就是这种情况)。

我不知道如何正确地进行研究,目前看来,测试setTimeout被调用的次数似乎是不可靠的。

答案 1 :(得分:1)

感谢@thabemmz研究了这一原因,我有一个共同的解决方案:

function countSetTimeoutCalls() {
  return setTimeout.mock.calls.filter(([fn, t]) => (
    t !== 0 ||
    !String(fn).includes('_flushCallback')
  ));
}

用法:

// expect(setTimeout).toHaveBeenCalledTimes(2);
// becomes:
expect(countSetTimeoutCalls()).toHaveLength(2);

应该很清楚代码在做什么;它会过滤掉所有来自react-test-renderer行的呼叫(即该函数包含_flushCallback且超时为0。

react-test-renderer的行为(甚至是函数命名)的变化很脆弱,但至少现在就可以了。