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?
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)
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();
});
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
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();
});
});