Reputation: 2221
Keep in mind, I'm fairly new to Jest, and I'm not deeply familiar with the internals of Node's Promise
. I get that async
functions are really functions that return a Promise
, and I've had good success with that thinking.
I have a function that I'm trying to test. It has a dependency that it imports before doing any other work. Depending on the input passed to foo
, that dependency may return different things which may or may not affect the result of foo
. It goes something like:
// foo.js
const someOtherWork = require("../bar/baz");
module.exports = async function foo() {
const array = await someOtherWork();
... do other things ...
return someStuff;
}
I import a function to help with the work that foo
is trying to do.
In my tests, I spy on the someOtherWork
function like so:
// foo.test.js
const someOtherWork = jest.requireActual("../bar/baz");
const mockSomeOtherWork = jest.fn().mockImplementation(someOtherWork);
jest.mock("../bar/baz", () => mockSomeOtherWork);
const foo = require("./foo.js");
// ...tests...
With this, I'm able to assert that mockSomeOtherWork
was called with specific parameters, and even that it returned the right output. Where I'm tripped up is when my mocked dependency (someOtherWork
in this case) returns a Promise
.
When I try to assert that the Promise
resolved with a specific value without calling the mock function in my test code, it always passes the test:
expect(mockSomeOtherWork).toHaveReturnedWith(Promise.resolve([my, array, of, expected, values])); // succeeds correctly
expect(mockSomeOtherWork).toHaveReturnedWith(Promise.reject("lol nope"); // succeeds ERRONEOUSLY
I've successfully failed tests when I assert that it returns an Array
, or anything else, like so:
expect(mockSomeOtherWork).toHaveReturnedWith(Array); // fails correctly
expect(mockSomeOtherWork).toHaveReturnedWith([my, array, of, expected, values]); // fails correctly
But any sort of Promise
passes the test, which means I can assume nothing from my assertions!
I'm starting to figure out that, to Jest, a Promise
is a mostly opaque object, and I must await
it or attach callbacks (via then
or catch
) to figure out what's inside. Do promises not remember their already-resolved value for me to inspect later, without re-calling the function that returned it? I don't want to have to rely on asserting other dependencies of someOtherWork
, or the return value of foo
, as those options assume a lot about foo
that might change at any time. I already have an effective spy-mock-thing in there on someOtherWork
. Should I instead grab the returned Promise
from mockSomeOtherWork.mock.results[0].value
and await
that? Is there a "correct" way to go about asserting the async
result of a dependency of a unit under test?
Upvotes: 4
Views: 4140
Reputation: 2221
Here's my workaround:
// If all is well, this should be a Promise
const results = mockSomeOtherWork.mock.results[0].value;
// If that Promise resolves to the right stuff, gucci!
const expected = [my, array, of, expected, values];
await expect(results).resolves.toStrictEqual(expected); // passes correctly
// This should fail, because the promise resolved to `expected`
const notExpected = [something, else];
await expect(results).resolves.toStrictEqual(notExpected); // fails correctly
I figure that, assuming Promise
is opaque, I do need to await
it to assert its value. I trust that no extra invocations or work will happen by me doing so, and I can prove this by asserting expect(mockSomeOtherWork).toHaveBeenCalledTimes(1)
. Since this assertion succeeds in the test both before and after my workaround above, that one call must be in the body of foo
.
It would be nice if Jest had a set of toHaveResolvedWith
matchers, but this works well enough for my purposes today.
Upvotes: 10