使用 Jest 和 Enzyme 测试 React 组件中的去抖动功能 [英] Testing debounced function in React component with Jest and Enzyme

查看:29
本文介绍了使用 Jest 和 Enzyme 测试 React 组件中的去抖动功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Jest 和 Enzyme 测试 React 组件,并且在测试去抖动函数是否被正确调用(或根本调用不正确)时遇到了困难.我已经简化了下面的组件代码(编辑使代码更简单),链接到 codepen 这里

//使用 lodash 去抖动class MyApp 扩展了 React.Component {构造函数(道具){极好的()this.state = {名称:初始值"};this.debouncedFunction = _.debounce(this.debouncedFunction, 3000);this.handleClick = this.handleClick.bind(this)}去抖动函数(){this.setState({name: 延迟后,更新值"});}句柄点击(){this.debouncedFunction();}使成为() {返回 (<div><p>{this.state.name}</p><button onClick={this.handleClick}>点击去抖动功能

);}}

我认为去抖动函数测试应该与非去抖动函数测试非常相似,但带有 setTimeoutPromise(带有 expect<.then.finally 中的/code> 断言).在尝试了采用这两种想法的多种测试变体之后,我不再那么确定了.有什么想法吗?

解决方案

注意:这个答案也适用于 lodash.throttle 因为它只是 debounce 的包装器.

Lodash 的 debounce 是个怪物,在测试中需要一些特殊处理因为它不仅使用 setTimeout(),而且还:

  • 调用 setTimeout() 递归:这意味着调用jest.runAllTimers()来模拟setTimeout 会导致无限递归错误,因为模拟的 setTimeout() 是同步执行的,直到它用完任务,这里不是这种情况.

  • 用途 日期 API:Jest v25 及以下仅模拟定时器函数(例如 setTimeout>,setInterval) 而 debounce 同时使用 setTimeoutDate 所以我们需要模拟它们.

你如何解决这个问题取决于你使用的笑话版本.

对于 Jest 25 及以下版本:

使用另一个库来模拟 Date 对象.在本例中,我将使用 jest- 中的 advanceBy()日期模拟

jest.useFakeTimers()等待行为(异步()=> {触发去抖动()AdvanceBy(DEBOUNCED_TIME + 1000)//转发日期jest.advanceTimersByTime(DEBOUNCED_TIME)//转发 setTimeout 的定时器})

Jest 版本 26:

Jest 第 26 版 为假定时器引入了现代模式,它模拟了两者Date 和定时器功能,这是一个选择加入的功能,所以为了使用它你需要在测试运行前添加 jest.useFakeTimers('modern')>

jest.useFakeTimers(现代")等待行为(异步()=> {触发去抖动()jest.advanceTimersByTime(DEBOUNCED_TIME)})

Jest 版本 27+:

根据此PR,Jest v27 将使用现代实现默认情况下,所以我们不需要明确指定.

jest.useFakeTimers()等待行为(异步()=> {触发去抖动()jest.advanceTimersByTime(DEBOUNCED_TIME)})

I am testing a React component using Jest and Enzyme, and am having difficulty testing that a debounced function is called properly (or at all). I've simplified the component code below (edited to make code even simpler), link to codepen here

// uses lodash debounce

class MyApp extends React.Component {
  constructor(props) {
    super()
    this.state = {name: "initial value"};
    this.debouncedFunction = _.debounce(this.debouncedFunction, 3000);
    this.handleClick = this.handleClick.bind(this)
  }
  
  debouncedFunction () {
    this.setState({name: "after delay, updated value"});
  }
  
  handleClick() {
    this.debouncedFunction();
  }
  
  render() {
    return (
      <div>
        <p>{this.state.name}</p>
        <button onClick={this.handleClick}>
          click for debounced function
        </button>
      </div>
    );
  }
}

I figured that the debounced function test should be pretty similar to one that is non-debounced, but with a setTimeout or Promise (with the expect assertion inside .then or .finally). After trying many variations of tests employing both those ideas, I'm not so sure anymore. Any ideas?

解决方案

NOTE: this answer also applies to lodash.throttle since it is just a wrapper of debounce.

Lodash's debounce is a monster and needs some special treatments in test because not only does it use setTimeout() but it also:

  • Calls setTimeout() recursively: This means calling jest.runAllTimers() to mock setTimeout will lead to infinite recursion error, since mocked setTimeout() executes synchronously until it runs out of task, which is not the case here.

  • Uses Date API: Jest v25 and below only mocks timer functions (e.g. setTimeout, setInterval) while debounce uses both setTimeout and Date so we need to mock both of them.

How you fix this problem depend on what version of jest you are using.

For jest version 25 and below:

Use another library to mock Date object. In this example I'll use advanceBy() from jest-date-mock

jest.useFakeTimers()

await act(async () => {
  triggerDebounced()
  advanceBy(DEBOUNCED_TIME + 1000) // forward Date
  jest.advanceTimersByTime(DEBOUNCED_TIME) // forward setTimeout's timer
})

Jest version 26:

Jest version 26 introduces modern mode for fake timers which mocks both Date and timer functions, it's an opt-in feature, so in order to use it you need to add jest.useFakeTimers('modern') before the test runs

jest.useFakeTimers("modern")

await act(async () => {
  triggerDebounced()
  jest.advanceTimersByTime(DEBOUNCED_TIME)
})

Jest version 27+:

According to this PR, Jest v27 will use the modern implementation by default so we don't need to specify it explicitly.

jest.useFakeTimers()

await act(async () => {
  triggerDebounced()
  jest.advanceTimersByTime(DEBOUNCED_TIME)
})

这篇关于使用 Jest 和 Enzyme 测试 React 组件中的去抖动功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆