DoMock doUnmock resetModules does not work as expected in browser mode.
Describe the bug
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { RoutePath } from "../constants";
import type {
NavigationGuardNext,
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
} from "vue-router";
describe("Router Guards", () => {
let mockTo: RouteLocationNormalized;
let mockNext: NavigationGuardNext;
let mockFrom: RouteLocationNormalizedLoaded;
beforeEach(() => {
mockTo = {
meta: {
requiresAuth: true,
},
path: "/test",
fullPath: "/test",
} as RouteLocationNormalized;
mockNext = vi.fn();
mockFrom = {
meta: {},
path: "/test",
fullPath: "/test",
} as RouteLocationNormalizedLoaded;
vi.resetModules();
vi.doUnmock("../../stores/userStore");
});
describe("authGuard", () => {
it("未登录用户访问需要认证的页面时应该重定向到登录页面", async () => {
vi.doMock("../../stores/userStore", () => ({
useUserStore: () => ({
user: undefined,
login: vi.fn(),
logout: vi.fn(),
roleCodes: undefined,
permissionCodes: undefined,
}),
}));
const { authGuard } = await import("../guards");
const result = authGuard(mockTo, mockFrom, mockNext);
expect(result).toEqual({
path: RoutePath.LOGIN,
query: { redirect: mockTo.fullPath },
});
const useUserStore1 = await import("../../stores/userStore");
console.log(useUserStore1.useUserStore());
});
it("已登录用户访问登录页面时应该重定向到后台页面", async () => {
const userInfo = {
id: 1,
name: "testUser",
};
vi.doMock("../../stores/userStore", () => ({
useUserStore: () => ({
user: userInfo,
login: vi.fn(),
logout: vi.fn(),
roleCodes: ["admin"],
permissionCodes: ["read"],
}),
}));
const { authGuard } = await import("../guards");
const useUserStore2 = await import("../../stores/userStore");
console.log(useUserStore2.useUserStore());
mockTo.path = RoutePath.LOGIN;
mockTo.meta.requiresAuth = false;
const result = authGuard(mockTo, mockFrom, mockNext);
expect(result).toEqual({
path: RoutePath.DASHBOARD,
});
});
});
});
During actual execution, the value of the second useUserStore2 is always the content of the first useUserStore1.
To my knowledge, even without using doUnmock or resetModules, doMock should be able to override the previous mock content. However, regardless of whether I use vi.resetModules(); vi.doUnmock("../../stores/userStore"); in beforeEach, the doMock in the second test cannot effectively override the content of the doMock in the first test.
Reproduction
Running guards.test.ts from this code reproduces this result
mjga-dashboard.zip
System Info
"devDependencies": {
"@playwright/test": "^1.51.0",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.13.9",
"@vitejs/plugin-vue": "^5.2.1",
"@vitest/browser": "^3.0.9",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^7.0.2",
"playwright": "^1.51.1",
"typescript": "~5.8.0",
"vite": "^6.2.1",
"vite-plugin-vue-devtools": "^7.7.2",
"vitest": "^3.0.8",
"vitest-browser-vue": "^0.2.0",
"vue-tsc": "^2.2.8"
}
Used Package Manager
npm
Validations
- [x] Follow our Code of Conduct
- [x] Read the Contributing Guidelines.
- [x] Read the docs.
- [x] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [x] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [x] The provided reproduction is a minimal reproducible example of the bug.
This doesn't work because resetModules doesn't work in the browser at the moment.
This doesn't work because
resetModulesdoesn't work in the browser at the moment.
Thank you. However, is doUnmock currently unavailable as well?
Additionally, as far as I know, even though doMock's default behavior overwrites the previous behavior (as mentioned in this issue), theoretically, this test should still work without using resetModules.
Browser mocking and Node.js mocking have completely different implementations. At the moment, browser mocking doesn't support subsequent mocks for the same reason it doesn't support resetModules - the cache is not invalidated.
Browser mocking and Node.js mocking have completely different implementations. At the moment, browser mocking doesn't support subsequent mocks for the same reason it doesn't support
resetModules- the cache is not invalidated.
I see. It looks like there's still a long way to go before I'll be able to use mock in browser mode, so I'm going to hold off on writing unit tests for a while until browser mode has perfected sub-sequent mock.
Ah, so that probably explains this error. I'm using vi.doMock in a test that I'm trying to rewrite into browser mode, but it throws.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock
❯ ManualMockedModule.resolve ../../node_modules/@vitest/mocker/dist/chunk-registry.js:160:24
❯ processTicksAndRejections node:internal/process/task_queues:95:5
❯ runNextTicks node:internal/process/task_queues:64:3
❯ process.processImmediate node:internal/timers:449:9
❯ process.callbackTrampoline node:internal/async_hooks:130:17
❯ node_modules/@vitest/browser/dist/webdriver-B1QbgqhC.js:128:35
❯ RouteHandler._handleInternal ../../node_modules/playwright-core/lib/client/network.js:697:23
❯ RouteHandler._handleImpl ../../node_modules/playwright-core/lib/client/network.js:666:14
❯ RouteHandler.handle ../../node_modules/playwright-core/lib/client/network.js:660:12
❯ Page._onRoute ../../node_modules/playwright-core/lib/client/page.js:144:23
Caused by: Error: [birpc] rpc is closed, cannot call "resolveManualMock"
❯ Proxy.sendCall node_modules/@vitest/browser/dist/index.js:2875:17
❯ ManualMockedModule.factory node_modules/@vitest/browser/dist/index.js:3212:34
❯ ManualMockedModule.resolve ../../node_modules/@vitest/mocker/dist/chunk-registry.js:158:25
❯ node_modules/@vitest/browser/dist/webdriver-B1QbgqhC.js:128:48
❯ RouteHandler._handleInternal ../../node_modules/playwright-core/lib/client/network.js:699:7
❯ RouteHandler._handleImpl ../../node_modules/playwright-core/lib/client/network.js:666:25
❯ ../../node_modules/playwright-core/lib/client/network.js:660:55
❯ AsyncLocalStorage.run node:async_hooks:346:14
❯ Zone.run ../../node_modules/playwright-core/lib/server/utils/zones.js:42:36
❯ NodeZone.run ../../node_modules/playwright-core/lib/server/utils/nodePlatform.js:57:23
❯ RouteHandler.handle ../../node_modules/playwright-core/lib/client/network.js:660:34
❯ Page._onRoute ../../node_modules/playwright-core/lib/client/page.js:144:42
❯ Proxy.<anonymous> ../../node_modules/playwright-core/lib/client/page.js:90:51
❯ Proxy._callHandler ../../node_modules/playwright-core/lib/client/eventEmitter.js:66:29
❯ Proxy.emit ../../node_modules/playwright-core/lib/client/eventEmitter.js:56:12
❯ Connection.dispatch ../../node_modules/playwright-core/lib/client/connection.js:176:21
❯ Immediate.<anonymous> ../../node_modules/playwright-core/lib/inProcessFactory.js:50:85
❯ process.processImmediate node:internal/timers:478:21
❯ process.callbackTrampoline node:internal/async_hooks:130:17