Caio Oliveira
Caio Oliveira

Reputation: 814

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

Upvotes: 3

Views: 6263

Answers (1)

Mulan
Mulan

Reputation: 135227

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.

Upvotes: 4

Related Questions