Reputation: 5671
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.
Upvotes: 1
Views: 2438
Reputation: 26
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!
Upvotes: 1