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:
- Using typescript 6.0.2
- With "useDefineForClassFields" turned on in the tsconfig
- Mocking a class with a class property
- In a file that imports a function whose name matches the class property
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
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
useDefineForClassFieldssetting 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:
This produces a ReferenceError, Cannot access
<import>before initialization. However, the line and character number are nonsense as the error occurs in generated code.The bug seems agnostic to how the class itself is defined. All of these trigger the bug:
Method declarations, however, do not trigger it. The following works without issue:
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.3Used Package Manager
pnpm
Validations