Search code examples
node.jstypescriptunit-testingjestjsts-jest

clearTimeout test not working with jest.useFakeTimers()


I'm trying to migrate to the "modern" version of jest.useFakeTimers(), which is not the default in version 27.x.
I'm having some issues with my tests, since Jest keeps saying that functions like clearTimeout and setInterval are not jest mocks:

// Poster.ts
// This is the class method I'm testing

startInterval(interval = 1800000) {
  this._interval && clearTimeout(this._interval)

  this._interval = (setInterval(
    () =>
      this.post()
        .then((result) => {
          this.runHandlers('autopostSuccess', result)
          return result
        })
        .catch((error) => this.runHandlers('autopostFail', error)),
    interval
  ) as unknown) as number
  return this._interval
}
// Poster.test.ts
describe('startInterval method', () => {
  const p = new Poster({ client: {} })
  beforeEach(() => {
    jest.useFakeTimers()
  })
  afterEach(() => {
    jest.useRealTimers()
  })

  it('should call clearTimeout (but only from the second use)', () => {
    const firstID = p.startInterval()
    expect(clearTimeout).not.toHaveBeenCalled() // This line fails with the following error
    p.startInterval()
    expect(clearTimeout).toHaveBeenCalledTimes(1)
    expect(clearTimeout).toHaveBeenLastCalledWith(firstID)
  })

  // ...
})

Error:

expect(received).not.toHaveBeenCalled()

Matcher error: received value must be a mock or spy function

So, it's complaining about the fact that clearTimeout is not a mock, even though I'm using jest.useFakeTimers()

Versions:

  • jest @ 27.0.3
  • ts-jest @ 27.0.2
  • typescript @ 4.2.4
  • Node @ 12.14.0

I'm sure I'm missing something here, maybe I'm using useFakeTimers in the wrong way, but neither the method docs nor the page on Timer Mocks have helped me.

Does anybody know why my test is not working?


Solution

  • After some digging I found hopefully correct approach.

    Answer is in this issue discussion.

    Jest added spies to the fake timers automatically. We no longer do that, people need to do e.g. jest.spyOn(global, 'setTimeout') themselves.

    And that works for me. Just add jest.spyOn(global, 'clearTimeout') and it should work. Maybe you even don't need jest.useFakeTimers() statement.