When we are using timer functions like setTimeout or setInterval, it is difficult to test them as the callback function is called after the certain time is passed. In async http api calls, we can mock the api call and test the success and error condition. In async functions like setTimeout or setInterval, we can also mock the timing to make it speed faster instead of waiting for it to over. Let us see how.

import React, { Component } from "react";
class Timers extends Component {
  constructor() {
    super();
    this.state = {
      index: 0
    };
    this.setTimeoutFn = this.setTimeoutFn.bind(this);
    this.setIntervalFn = this.setIntervalFn.bind(this);
  }
  setTimeoutFn() {
    setTimeout(() => {
      this.setState({
        index: 1
      });
    }, 1000);
  }
  setIntervalFn() {
    setInterval(() => {
      this.setState({
        index: this.state.index + 1
      });
    }, 1000);
  }
  render() {
    return <div></div>;
  }
}
export default Timers;

In the above function, there are two functions - setTimeoutFn which starts the setTimeout and setIntervalFn which starts the setInterval. We have a state variable index which is being updated in the callback function of the setTimeout and setInterval. Let us see the unit test code using enzyme.

import React from "react";
import { shallow } from "enzyme";
import Timers from "../Timers";
describe("settimeout fn", () => {
  it("should increment index by 1 after 1 second (using advanceTimersByTime)", () => {
    const component = shallow(<Timers />);
    jest.useFakeTimers();
    expect(component.state("index")).toEqual(0);
    component.instance().setTimeoutFn();
    jest.advanceTimersByTime(1000);
    expect(component.state("index")).toEqual(1);
    jest.useRealTimers();
  });
  it("should increment index by 1 after 1 second (using runAllTimers)", () => {
    const component = shallow(<Timers />);
    jest.useFakeTimers();
    expect(component.state("index")).toEqual(0);
    component.instance().setTimeoutFn();
    jest.runAllTimers(); // you can also use run.runOnlyPendingTimers()
    expect(component.state("index")).toEqual(1);
    jest.useRealTimers();
  });
});
describe("setinterval fn", () => {
  it("should increment index by 1 after 1 second (using advanceTimersByTime)", () => {
    const component = shallow(<Timers />);
    jest.useFakeTimers();
    expect(component.state("index")).toEqual(0);
    component.instance().setIntervalFn();
    jest.advanceTimersByTime(1000);
    expect(component.state("index")).toEqual(1);
    jest.useRealTimers();
  });
  it("should increment index by 1 after 1 second (using runOnlyPendingTimers)", () => {
    const component = shallow(<Timers />);
    jest.useFakeTimers();
    expect(component.state("index")).toEqual(0);
    component.instance().setIntervalFn();
    jest.runOnlyPendingTimers(); // using runAllTimers will be error as setInterval never
    // finishes. So it will be infinite loop.
    expect(component.state("index")).toEqual(1);
    jest.useRealTimers();
  });
});

Following points are to be noted from the above unit test code

  • There is no need to wait for the interval time to be over to test the callback function code. We can use fake timers to advance the time. Following is the code to use the fake timers.
jest.useFakeTimers();
  • The time can be advanced using following code
jest.advanceTimersByTime(1000);
  • For testing the interval function callback code, we use runPendingTimers. If we use something like runAllTimersto wait for all the timers to run, then it will result in infinite loop in setInterval.
jest.runOnlyPendingTimers(); 
  • If at any point, you want to revert back to using real timers, then use the following code
jest.useRealTimers();