简体   繁体   中英

Promise not rejecting correctly in Jest test

I am trying to test a console.error output when a promise rejects using Jest. I am finding that the promise seems to be resolving after my test has run, causing the test to fail.

Example Function:

export default function doSomething({ getData }) {

  const success = data => {
    //do stuff with the data
  }
  const handleError = () => {
    //handle the error
  }

  getData.then(response => success(response)).catch(error => {
    console.error(error)
    handleError()
  })

}

Example Test File:

import doSomething from "doSomething"

it("should log console.error if the promise is rejected", async () => {
  const getData = new Promise((resolve, reject) => {
    reject("fail");
  });
  global.console.error = jest.fn();
  await doSomething({ getData });
  expect(global.console.error).toHaveBeenCalledWith("fail");
})
//fails with global.console.error has not been called

When I was exploring the problem, I noticed that if I add in a console.log and await that, it works.

This will pass...

import doSomething from "doSomething"

it("should log console.error if the promise is rejected", async () => {
  const getData = new Promise((resolve, reject) => {
    reject("fail");
  });
  global.console.error = jest.fn();
  await doSomething({ getData });
  await console.log("anything here");
  expect(global.console.error).toHaveBeenCalledWith("fail");
})

How do I test for this correctly? Should I refactor how my getData function is being called? It needs to get called as soon as the doSomething function is called.

Why is the original test failing?

The trick for understanding why the first test example won't pass is in digging into what the await operator is actually doing. From the Mozilla docs :

[rv] = await expression;
  • expression - A Promise or any value to wait for.
  • rv - Returns the fulfilled value of the promise, or the value itself if it's not a Promise.

In your first test the value of expression is the return value of the doSomething function. You aren't returning anything from this function, so the return value will be undefined . This isn't a Promise, so nothing for await to do, it will just return undefined and move on. The expect statement will then fail, as you haven't actually awaited the inner promise: getData.then(...).catch(...) .

To fix the test, without adding in the extra line, await console.log("anything here"); , simply return the inner promise from the doSomething function, so that the await operator will actually operate on the Promise.

export default function doSomething({ getData }) {
  return getData.then(...).catch(...);
  ...
}

Is this the right way to test this?

I don't think that there is anything majorly wrong with how the doSomething function is written. This kind of dependency injecting usually makes functions easier to test than trying to mock out the inner workings of a function.

I would only recognise though that because you are injecting a Promise ( getData ), and resolving it within the function, you have made the doSomething function asynchronous (which is what made it more complicated to test).

Had you resolved the Promise and then called doSomething on the value it resolves to, getData.then(doSomething).catch(handleError) , your doSomething function would have been synchronous and easier to test. I would also say that writing it this way makes it much more verbose that something asynchronous is going on, whereas the original, doSomething({ getData }) , hides that within the doSomething function body.

So nothing strictly incorrect, but maybe a few things to think about that might make testing easier and code more verbose. I hope that helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM