使用 Jest 和 Enzyme 测试 React 组件中的去抖动功能 [英] Testing debounced function in React component with Jest and Enzyme
问题描述
我正在使用 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}>点击去抖动功能按钮>
);}}
我认为去抖动函数测试应该与非去抖动函数测试非常相似,但带有 setTimeout
或 Promise
(带有 expect<
.then
或 .finally
中的/code> 断言).在尝试了采用这两种想法的多种测试变体之后,我不再那么确定了.有什么想法吗?
注意:这个答案也适用于 lodash.throttle
因为它只是 debounce
的包装器.
Lodash 的 debounce
是个怪物,在测试中需要一些特殊处理因为它不仅使用 setTimeout()
,而且还:
调用
setTimeout()
递归:这意味着调用jest.runAllTimers()
来模拟setTimeout 会导致无限递归错误,因为模拟的setTimeout()
是同步执行的,直到它用完任务,这里不是这种情况.用途
日期
API:Jest v25 及以下仅模拟定时器函数(例如setTimeout>
,setInterval) 而debounce
同时使用setTimeout
和Date
所以我们需要模拟它们.
你如何解决这个问题取决于你使用的笑话版本.
对于 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 callingjest.runAllTimers()
to mocksetTimeout
will lead to infinite recursion error, since mockedsetTimeout()
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
) whiledebounce
uses bothsetTimeout
andDate
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屋!