【问题标题】:How to test a delayed event handler with requestAnimationFrame in Jest?如何开玩笑地测试延迟的事件处理程序?
【发布时间】:2021-06-05 16:29:44
【问题描述】:

我有一个应该在不久的将来执行的函数,我想开玩笑地测试它,但不知道如何正确地执行此操作。实际上,我正在使用 React + 测试反应库,但这并没有改变我认为的任何东西。
这是函数本身:

let start;
const holdClick = (el, delayedClickHandler, time = 0) => {
  if (time === 0) return delayedClickHandler();
  let counter = 0;
  function countToExecute() {
    if (counter === time) delayedClickHandler();
    else {
      start = requestAnimationFrame(countToExecute);
      counter += 1;
    }
  }

  start = window.requestAnimationFrame(countToExecute);

  el.ontouchend = () => {
    window.cancelAnimationFrame(start);
  };
};

我的组件

function Component() {
  const [clicked, setClicked] = useState(false);

  return (
    <span
      // data attribute must be changed to "active" after 30 ticks
      data-testid={clicked ? 'active' : 'static'}
      onTouchStart={e => holdClick(e.target, () => setClicked(true), 30)}
    >
      click me
    </span>
  );
}

还有我的测试

test('execute delayed click', () => {
  beforeEach(() => {
    jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
  });

  const { getByTestId } = render(<App />);
  const el = getByTestId('static');

  fireEvent.click(el);

  jest.useFakeTimers();
  setTimeout(() => {
    expect(el).toHaveAttribute('data-testid', 'active');
  }, 10000);
  jest.runAllTimers();
});

我的猜测是问题出在requestAnimationFrame,但正如我之前提到的,我几乎不知道有什么方法可以解决这个问题。我尝试添加jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb =&gt; cb());,但它没有改变任何东西。 所以我想知道是否有不需要麻烦的模拟 window 对象的解决方案。

【问题讨论】:

    标签: javascript reactjs testing jestjs react-testing-library


    【解决方案1】:

    首先,您的回调设置在 onTouchStart 属性上,这意味着您需要触发 touchStart 事件才能触发它。

    fireEvent.touchStart(el);
    

    其次,在您的countToExecute 函数中,counter 变量应该在requestAnimationFrame 调用之前增加,否则counter 将永远无法增加。这是因为实际的 requestAnimationFrame 在每次重绘(每个“滴答”)时都会调用它的回调,但由于它在测试中被模拟,所以回调会立即被调用。

    function countToExecute() {
        if (counter === time) {
            delayedClickHandler();
        } else {
          counter += 1;
          start = requestAnimationFrame(countToExecute);
        }
    }
    

    您的测试应如下所示:

    test('execute delayed click', () => {
        jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
        const { getByTestId } = render(<MyComponent />);
        const el = getByTestId('static');
        fireEvent.touchStart(el);
        expect(el).toHaveAttribute('data-testid', 'active');
    });
    

    【讨论】:

    • 哦,我忘了重构和更改点击触摸。您的代码实际上对我有用,但老实说,我不明白为什么在动画请求之前增加计数器会产生这种差异。该代码在浏览器中完美运行,counter 确实增加了,那么在 Node 环境中会发生什么?这个模拟不应该返回 countToExecute 函数并在范围内更新 counter 吗?我的意思是无论如何都必须增加,不是吗?
    • 原始的requestAnimationFrame 在每次重绘(每个“滴答声”)时调用它的回调,但在测试中它会立即调用,因为我们正在模拟它。进行此更改不应影响实际行为,并且在逻辑上也有意义,因为您要确保在再次执行 countToExecute 之前已增加 counter
    • 哦,这正是我的想法。感谢您的清晰解释!
    猜你喜欢
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2020-10-23
    • 2018-01-10
    • 1970-01-01
    • 2018-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多