2

I'm trying to mock console.info which I know will be called when an imported function runs. The function consists entirely of a single fetch which, when not running in production, reports the request and response using console.info.

At the question Jest. How to mock console when it is used by a third-party-library?, the top-rated answer suggests overwriting global.console, so I'm using jest.spyOn to try that out:

import * as ourModule from "../src/ourModule";

test("Thing", () => {
    // Tested function requires this. Including it here in case it's causing
    // something quirky that readers of this question may know about
    global.fetch = require("jest-fetch-mock");

    const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
        () => { console.error("mockInfo") }
    );

    ourModule.functionBeingTested("test");
    expect(mockInfo).toHaveBeenCalled();
}

As expected, the output contains an instance of "mockInfo". However, then testing that with toHaveBeenCalled() fails.

expect(jest.fn()).toHaveBeenCalled()

Expected mock function to have been called, but it was not called.

  40 |
  41 |     ourModule.functionBeingTested("test");
> 42 |     expect(mockInfo).toHaveBeenCalled();
     |                      ^
  43 | 

  at Object.toHaveBeenCalled (__tests__/basic.test.js:42:22)

console.error __tests__/basic.test.js:38
  mockInfo

I've tried moving the spyOn to before the module is loaded, as suggested in one of the comments on the answer, with no difference in result. What am I missing here?

Here's the function in question:

function functionBeingTested(value) {
    const fetchData = {
        something: value
    };

    fetch("https://example.com/api", {
        method: "POST",
        mode:   "cors",
        body:   JSON.stringify(fetchData),
    })
        .then( response => {
            if (response.ok) {
                if (MODE != "production") {
                    console.info(fetchData);
                    console.info(response);
                }
            } else {
                console.error(`${response.status}: ${response.statusText}`);
            }
        })
        .catch( error => {
            console.error(error);
        });
}
Scott Martin
  • 1,260
  • 2
  • 17
  • 27

1 Answers1

3

Issue

console.info is called in a Promise callback which hasn't executed by the time ourModule.functionBeingTested returns and the expect runs.

Solution

Make sure the Promise callback that calls console.info has run before running the expect.

The easiest way to do that is to return the Promise from ourModule.functionBeingTested:

function functionBeingTested(value) {
  const fetchData = {
    something: value
  };

  return fetch("https://example.com/api", {  // return the Promise
    method: "POST",
    mode: "cors",
    body: JSON.stringify(fetchData),
  })
    .then(response => {
      if (response.ok) {
        if (MODE != "production") {
          console.info(fetchData);
          console.info(response);
        }
      } else {
        console.error(`${response.status}: ${response.statusText}`);
      }
    })
    .catch(error => {
      console.error(error);
    });
}

...and wait for it to resolve before asserting:

test("Thing", async () => {  // use an async test function...
  // Tested function requires this. Including it here in case it's causing
  // something quirky that readers of this question may know about
  global.fetch = require("jest-fetch-mock");

  const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
      () => { console.error("mockInfo") }
  );

  await ourModule.functionBeingTested("test");  // ...and wait for the Promise to resolve
  expect(mockInfo).toHaveBeenCalled();  // SUCCESS
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111