vitest icon indicating copy to clipboard operation
vitest copied to clipboard

DoMock doUnmock resetModules does not work as expected in browser mode.

Open ccmjga opened this issue 10 months ago • 5 comments

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

ccmjga avatar Mar 21 '25 07:03 ccmjga

This doesn't work because resetModules doesn't work in the browser at the moment.

sheremet-va avatar Mar 21 '25 07:03 sheremet-va

This doesn't work because resetModules doesn'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.

ccmjga avatar Mar 21 '25 08:03 ccmjga

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.

sheremet-va avatar Mar 21 '25 08:03 sheremet-va

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.

ccmjga avatar Mar 23 '25 06:03 ccmjga

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

KubaJastrz avatar Jun 10 '25 10:06 KubaJastrz