Search code examples
javascriptpromisejestjstimeout

Test Promise with timeout Jest


I have a simple code that resolves after 1 second:

const promiseWithTimeout = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({id: 3});
    }, 1000);
  });
};

I am trying to make a test using jest, however, using resolves or awaits makes it so the test passes even with the wrong expectation (unhandled rejection) . And If I try to make a failing test, it passes even if the expectation fails

What I tried

it("Should return the right ID", ()=>{
    expect.assertions(1);
    expect(promiseWithTimeout()).resolves.toEqual({id: 3});    // this one works fine
    jest.advanceTimersByTime(1000)

  })
it("Should return the right ID", ()=>{
    expect.assertions(1);
    expect(promiseWithTimeout()).resolves.toEqual({id: 4});    // this PASSES , but shows 
    // UnhandledPromiseRejectionWarning: Error: expect(received).resolves.toEqual(expected) // deep equality
    jest.advanceTimersByTime(1000)

  })

I expected it to fail and show that the objects are different.

I also tried using then instead, but still same problem


Solution

  • You have a few options -

    return a promise from your test

    This will pass -

    it("should return the id", () => {
      return promiseWithTimeout().then(m => {  // return
        expect(m.id).toBe(3)                   // ✓
      })
    })
    

    This will fail -

    it("should return the id", () => {
      return promiseWithTimeout().then(m => {  // return
        expect(m.id).toBe(4)                   // ✕ id 4
      })
    })
    

    async..await

    This will pass -

    it("should return the id", async () => {   // async
      const m = await promiseWithTimeout()     // await
      expect(m.id).toBe(3)                     // ✓
    })
    

    This will fail -

    it("should return the id", async () => {
      const m = await promiseWithTimeout()
      expect(m.id).toBe(4)                     // ✕ id 4
    })
    

    .resolves and .rejects

    Notice you must return then expectant promise. This will pass -

    it("should return the id", ()=>{
      return expect(promiseWithTimeout()).resolves.toEqual({id: 3}) // ✓
    })
    

    This will fail -

    it("should return the id", ()=>{
      return expect(promiseWithTimeout()).resolves.toEqual({id: 4}) // ✕ id 4
    })
    

    using jest.advanceTimersByTime

    Jest allows you to "fake" the timers so test can run fast while still ensuring async code is behaving correctly. In your testing file you must include jest.useFakeTimers() and your tests must use one of the techniques above. Here we return expectant promises -

    jest.useFakeTimers()
    
    it("should return the id", () => {
      const p = promiseWithTimeout()
      jest.advanceTimersByTime(1000)               // advance timers
      return expect(p).resolves.toEqual({ id: 3 }) // ✓
    })
    

    This will fail -

    jest.useFakeTimers()
    
    it("should return the id", () => {
      const p = promiseWithTimeout()
      jest.advanceTimersByTime(1000)
      return expect(p).resolves.toEqual({ id: 4 })  // ✕ id 4
    })
    

    using expect.assertions

    In many cases you do not need to specify expect.assertions. For example, when using the .resolves expectation, we will get an assertion failure if the promise rejects -

    const promiseWithTimeout = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject({ id: 3 })                       // reject
        }, 1000)
      })
    }
    
    jest.useFakeTimers()
    
    it("should return the id", () => {
      const p = promiseWithTimeout()
      jest.advanceTimersByTime(1000)
      return expect(p).resolves.toEqual({ id: 3 }) // ✕ rejected
    })
    

    If you expect a promise to be rejected, use the .catch method and then add expect.assertions to verify that a certain number of assertions are called. Otherwise, a fulfilled promise would not fail the test -

    test('the fetch fails with an error', () => {
      expect.assertions(1)                      // expect assertions
      return fetchData().catch(e => expect(e).toMatch('error')) // ✓
    })
    

    For more information, see Testing Asynchronous Code from the Jest docs.