开玩笑:setTimeout 被调用了太多次

Posted

技术标签:

【中文标题】开玩笑:setTimeout 被调用了太多次【英文标题】:Jest: setTimeout is being called too many times 【发布时间】:2019-09-11 06:38:27 【问题描述】:

我正在测试一个使用setTimeoutreact 组件。 问题是 JestsetTimeout 被调用,即使它显然不是。有一个 setTimeout 可以从 ui 中删除某些内容,另一个可以暂停计时器鼠标悬停在组件上。

我尝试在setTimeout 所在的位置添加console.log(),并且从不调用控制台日志,这意味着未调用应用程序中的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 不应该被调用,但我收到它调用一次。在第二个测试中,setTimeout 应该被调用一次,但被调用了 3 次。 该应用程序运行良好我只是不明白jest 发生了什么,这表明setTimeout 被调用的次数超过是的。

【问题讨论】:

这里还有解决方案吗?... 【参考方案1】:

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

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

我不知道如何正确地继续研究这个问题,目前看来,测试 setTimeout 被调用的次数似乎不可靠。

【讨论】:

谢谢你;我用你发现的东西创建了一个(一起破解的)解决方法,发布在下面。【参考方案2】:

感谢@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 的行为(甚至函数命名)的变化很脆弱,但至少现在可以解决问题。

【讨论】:

以上是关于开玩笑:setTimeout 被调用了太多次的主要内容,如果未能解决你的问题,请参考以下文章

带有 Typescript 错误的玩笑:超时 - 在 jest.setTimeout.Timeout 指定的 5000 毫秒超时内未调用异步回调

setTimeout()和setInterval() 何时被调用执行(非多线程).RP

setTimeout和setInterval的区别

setTimeout与setInterval

H2 DB Initial Set Up 脚本在 JUNIT 中被多次调用

setTimeout()