ReferenceError / crash when a class property inside a mocked module matches the name of an import (TypeScript + useDefineForClassFields) (original) (raw)

Describe the bug

When a mock defines a class with a property key that matches that of an import, vitest incorrectly hoists the imported reference above that mock, while leaving the instantiation below it.

The bug is triggered with the following conditions:

The useDefineForClassFields setting is a requirement for this bug. Without this setting, typescript writes the property set inside the constructor, where presumably vitest knows it is a property name and not a reference. With this setting on, typescript emits code more-or-less as written.

I tried to reproduce this with pure javascript, but after a few tries I wasn't able to. It seems to be typescript specific.

Reproduction

Full reproduction: https://github.com/SunsetFi/vitest-class-method-name-collision/tree/main
Failing test in CI: https://github.com/SunsetFi/vitest-class-method-name-collision/actions/runs/24091793731/job/70279852955

Sample test:

const { myMethodMock } = vi.hoisted(() => ({ myMethodMock: vi.fn(), }));

vi.mock("./MyClass", () => ({ MyClass: class { // This line triggers the crash myMethod = myMethodMock; // This workaround fixes it: // ["myMethod"] = myMethodMock; }, }));

// This incorrectly gets hoisted above the MyClass mock, // presumably because vitest sees the property name and thinks its accessing // this function. import { myMethod } from "./my-method";

describe("myMethod", () => { it("should call MyClass.myMethod", () => { myMethodMock.mockReturnValue("Mocked Hello, World!"); const result = myMethod(); expect(myMethodMock).toHaveBeenCalled(); expect(result).toBe("Mocked Hello, World!"); }); });

This produces a ReferenceError, Cannot access <import> before initialization. However, the line and character number are nonsense as the error occurs in generated code.

ReferenceError: Cannot access '__vi_import_0__' before initialization
 ❯ src/my-method.spec.ts:4:18
      2|
      3| const { myMethodMock } = vi.hoisted(() => ({
      4|   myMethodMock: vi.fn(),
       |                  ^
      5| }));

The bug seems agnostic to how the class itself is defined. All of these trigger the bug:

vi.mock("./MyClass", () => ({ MyClass: class { myMethod = myMethodMock; }, }));

vi.mock("./MyClass", () => ({ MyClass: class MyClass { myMethod = myMethodMock; }, }));

vi.mock("./MyClass", () => { class MyClass { myMethod = myMethodMock; } return { MyClass }; });

Method declarations, however, do not trigger it. The following works without issue:

vi.mock("./MyClass", () => ({ MyClass: class { myMethod() { return myMethodMock(); } }, }));

System Info

System: OS: Linux 6.19 Arch Linux CPU: (16) x64 AMD Ryzen 7 7840HS w/ Radeon 780M Graphics Memory: 15.06 GB / 30.65 GB Container: Yes Shell: 5.9 - /bin/zsh Binaries: Node: 24.14.1 - /home/sunset/.vite-plus/js_runtime/node/24.14.1/bin/node npm: 11.11.0 - /home/sunset/.vite-plus/js_runtime/node/24.14.1/bin/npm pnpm: 10.17.1 - /home/sunset/.local/share/pnpm/pnpm Deno: 2.7.11 - /usr/bin/deno Browsers: Firefox: 149.0 Firefox Developer Edition: 149.0 npmPackages: vitest: ^4.1.3 => 4.1.3

Used Package Manager

pnpm

Validations