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 (eg 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.
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 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)
})
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)
})
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.