How to clear/reset mocks in Vitest
Asked Answered
H

2

18

I have a simple composable useRoles which I need to test

import { computed } from "vue";
import { useStore } from "./store";

export default function useRoles() {
  const store = useStore();

  const isLearner = computed(() => store.state.profile.currentRole === "learner");

  return {
    isLearner
  };
}

My approach of testing it is the following

import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";

describe("useRoles", () => {
  afterEach(() => {
    vi.clearAllMocks();
    vi.resetAllMocks();
  });

  it("should verify values when is:Learner", () => { // works
    vi.mock("./store", () => ({
      useStore: () => ({
        state: {
          profile: {
            currentRole: "learner"
          },
        },
      }),
    }));

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeTruthy();
  });

  it("should verify values when is:!Learner", () => { //fails
    vi.mock("./store", () => ({
      useStore: () => ({
        state: {
          profile: {
            currentRole: "admin"
          },
        },
      }),
    }));

    const { isLearner } = useRoles(); // Values are from prev mock

    expect(isLearner.value).toBeFalsy();
  });
});

And useStore is just a simple function that I intended to mock

export function useStore() {
  return {/**/};
}

The first test runs successfully, it has all the mock values I implemented but the problem is that it's not resetting for each test (not resetting at all). The second test has the old values from the previous mock.

I have used

vi.clearAllMocks();
vi.resetAllMocks();

but for some reason clear or reset is not happening.

How can I clear vi.mock value for each test?

Solution

As it turned out I should not be called vi.mock multiple times. That was the main mistake

Substitutes all imported modules from provided path with another module. You can use configured Vite aliases inside a path. The call to vi.mock is hoisted, so it doesn't matter where you call it. It will always be executed before all imports.

Vitest statically analyzes your files to hoist vi.mock. It means that you cannot use vi that was not imported directly from vitest package (for example, from some utility file)

Docs

My fixed solution is below.

import useRoles from "./useRoles";
import { useStore } from "./store"; // Required the mock to work

vi.mock("./store");

describe("useRoles", () => {
  afterEach(() => {
    vi.clearAllMocks();
    vi.resetAllMocks();
  });

  it("should verify values when is:Learner", () => {
    // @ts-ignore it is a mocked instance so we can use any vitest methods
    useStore.mockReturnValue({
      state: {
        profile: {
          currentRole: "learner",
        },
      },
    });

    // Or as @wilmol pointed out it can be done without @ts-ignore
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: "learner",
        },
      },
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeTruthy();
  });

  it("should verify values when is:!Learner", () => {
    // You need to use either `vi.mocked(useStore)` or @ts-ignore or manually typecast it
    (<MockedFunction<typeof useStore>>useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: "admin",
        },
      },
    });

    // However, Vitest cleaner alternative is the following
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: "admin",
        },
      },
    });
    const { isLearner } = useRoles();

    expect(isLearner.value).toBeFalsy();
  });
});

vitest = v0.23.0

Halflight answered 6/9, 2022 at 10:54 Comment(0)
D
2

Wrap with vi.mocked so you don't need the @ts-ignore (or @ts-expect-error).

Also I found I don't need the beforeEach to clear/reset mocks.

Full code:

import { vi, describe, it, expect } from 'vitest';
import useRoles from './useRoles';
import { useStore } from './store';

vi.mock('./store');

describe('useRoles', () => {
  it('should verify values when is:Learner', () => {
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: 'learner',
        },
      },
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeTruthy();
  });

  it('should verify values when is:!Learner', () => {
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: 'admin',
        },
      },
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeFalsy();
  });
});
Dorene answered 26/6 at 10:22 Comment(1)
It is an excellent way to make TS happy.Halflight
B
9

I ran into the same issue and was able to find a workaround. In your case it should look like this:

import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";

vi.mock("./store");

describe("useRoles", () => {
  it("should verify values when is:Learner", async () => {
    const store = await import("./store");
    store.useStore = await vi.fn().mockReturnValueOnce({
      useStore: () => ({
        state: {
          profile: {
            currentRole: "learner"
          },
        },
      }),
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeTruthy();
  });

  it("should verify values when is:!Learner", async () => {
    const store = await import("./store");
    store.useStore = await vi.fn().mockReturnValueOnce({
      useStore: () => ({
        state: {
          profile: {
            currentRole: "admin"
          },
        },
      }),
    });

    const { isLearner } = useRoles(); // Value is from current mock

    expect(isLearner.value).toBeFalsy();
  });
});
Bangalore answered 2/11, 2022 at 9:17 Comment(3)
Havn't tested it but as a workaround, I believe the dynamic imports should do the trick. ThanksHalflight
Did not work for mocked API calls, but thanks for thisJudicious
Yeah, stumbled upon that problem as well @jchua, see another question of mine. Feel free to answer it, if you found a solution.Bangalore
D
2

Wrap with vi.mocked so you don't need the @ts-ignore (or @ts-expect-error).

Also I found I don't need the beforeEach to clear/reset mocks.

Full code:

import { vi, describe, it, expect } from 'vitest';
import useRoles from './useRoles';
import { useStore } from './store';

vi.mock('./store');

describe('useRoles', () => {
  it('should verify values when is:Learner', () => {
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: 'learner',
        },
      },
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeTruthy();
  });

  it('should verify values when is:!Learner', () => {
    vi.mocked(useStore).mockReturnValue({
      state: {
        profile: {
          currentRole: 'admin',
        },
      },
    });

    const { isLearner } = useRoles();

    expect(isLearner.value).toBeFalsy();
  });
});
Dorene answered 26/6 at 10:22 Comment(1)
It is an excellent way to make TS happy.Halflight

© 2022 - 2024 — McMap. All rights reserved.