AverageHelper
AverageHelper

Reputation: 2221

Expecting the resolved value of a mock inside of a function under test

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

Answers (1)

AverageHelper
AverageHelper

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

Related Questions