Search code examples
jestjsvitevitest

Vitest: How can I mock a function that the function being tested uses?


For example, how can I mock the hello function and .mockReturnValue("Hi")? Hoping someone can assist with this simple example as it's quite confusing to me currently.

// greet.ts
export function hello() {
  return "Hello ";
}

export function greet(name: string) {
  return hello() + name;
}

Test file:

// greet.spec.ts
import {hello, greet} from "./greet";

vi.mock("./greet", async () => {
  const mod = await vi.importActual<typeof 
    import("./greet")>("./greet");
  const hello = vi.fn().mockReturnValue("Hi ");
  return {
    ...mod,
    hello,
    // greet: vi.fn((name: string)=> hello() + name),
    // ^this works if I uncomment it, but it defeats 
    // the purpose of the test. 
    // Is it just not possible to do what I want? 
  }
})

describe("Greeting", () => {
  test("should return a personalized greeting", async () => {
    const result = greet("John");

    expect(result).toBe("Hi John");
    expect(hello).toHaveBeenCalled();
  });
});

Running the test still gives Hello John and the hello function is not called. No surprise... how can I mock the implementation of hello() in the actual greet function?


Solution

  • You probably meant mock() and not doMock(). doMock() doesn't do the hoisting that mock() does which means by the time the mock takes effect it's too late since the real ./greet is already in memory as it's imported at the top.

    Whilst this bug would prevent your mock from applying entirely in this situation, it's also clear what you are trying to fundamentally achieve isn't really possible with the code architecture in its current form.

    If you mock hello with some mock function, calling hello in the test will of course call that mock. However, the version of hello called by the other exported method greet will remain as the original un-mocked one.

    Mocks don't mutate the "real" module. They can be viewed almost as a proxy for the real thing. Internally, the module has no idea about your mock.

    It can at first seem odd to do this, but the usual way out of this situation isn't to try to find some hacky workaround that works with the existing code, but to instead change your code architecture to be more testable. Usually, making code more testable leads to higher-quality code.

    Your case is a test case that's not "real" so it's very hard to recommend a best practice for modelling the problem which is testable, though to avoid repeating previous answers, some example patterns are available on this answer.