Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing debounced function in React component with Jest and Enzyme

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?

like image 731
TDB Avatar asked Oct 08 '18 04:10

TDB


People also ask

How to unit test react with jest and enzyme?

For unit testing with Jest, first, install Jest using the following command – After installing Jest, add the following lines to your package.json file – Enzyme is also an open-source testing framework which is maintained by Airbnb. It is easier to assert, manipulate, and traverse React components in Enzyme.

How to test React component rendering with jest?

Jest also offers “snapshot testing” to verify the component rendering result. After installing Jest, add the following lines to your package.json file – Enzyme is also an open-source testing framework which is maintained by Airbnb. It is easier to assert, manipulate, and traverse React components in Enzyme.

What is enzyme testing for react?

This is where Enzyme comes in. Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output. You can write assertion tests for certain properties and values in your component.

What is the use of jest in testing?

Jest acts as a test runner, assertion library, and mocking library. Jest also provides Snapshot testing, the ability to create a rendered ‘snapshot’ of a component and compare it to a previously saved ‘snapshot’. The test will fail if the two do not match.


1 Answers

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)
})
like image 167
NearHuscarl Avatar answered Oct 18 '22 21:10

NearHuscarl