Skip to content

Commit faf32af

Browse files
committed
fix(@angular/build): explicitly fail when using Vitest runtime mocking
The Angular unit-test builder pre-bundles tests, which prevents Vitest's runtime mocking features (like `vi.mock`) from working correctly as they rely on module graph manipulation that occurs before bundling. To prevent confusion and silent failures, a patch is now injected that causes `vi.mock` and related methods to throw a descriptive error explaining the limitation and suggesting the use of Angular TestBed for mocking instead. Fixes #31609
1 parent 34c8583 commit faf32af

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export async function getVitestBuildOptions(
113113
}
114114

115115
entryPoints.set('init-testbed', 'angular:test-bed-init');
116+
entryPoints.set('vitest-mock-patch', 'angular:vitest-mock-patch');
116117

117118
// The 'vitest' package is always external for testing purposes
118119
const externalDependencies = ['vitest'];
@@ -153,10 +154,22 @@ export async function getVitestBuildOptions(
153154
buildOptions.polyfills,
154155
);
155156

157+
const mockPatchContents = `
158+
import { vi } from 'vitest';
159+
const error = new Error(
160+
'The "vi.mock" and related methods are not supported with the Angular unit-test system. Please use Angular TestBed for mocking.');
161+
vi.mock = () => { throw error; };
162+
vi.doMock = () => { throw error; };
163+
vi.importMock = () => { throw error; };
164+
vi.unmock = () => { throw error; };
165+
vi.doUnmock = () => { throw error; };
166+
`;
167+
156168
return {
157169
buildOptions,
158170
virtualFiles: {
159171
'angular:test-bed-init': testBedInitContents,
172+
'angular:vitest-mock-patch': mockPatchContents,
160173
},
161174
testEntryPointMappings: entryPoints,
162175
};

packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export class VitestExecutor implements TestExecutor {
147147
private prepareSetupFiles(): string[] {
148148
const { setupFiles } = this.options;
149149
// Add setup file entries for TestBed initialization and project polyfills
150-
const testSetupFiles = ['init-testbed.js', ...setupFiles];
150+
const testSetupFiles = ['init-testbed.js', 'vitest-mock-patch.js', ...setupFiles];
151151

152152
// TODO: Provide additional result metadata to avoid needing to extract based on filename
153153
if (this.buildResultFiles.has('polyfills.js')) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { execute } from '../../index';
2+
import {
3+
BASE_OPTIONS,
4+
describeBuilder,
5+
UNIT_TEST_BUILDER_INFO,
6+
setupApplicationTarget,
7+
} from '../setup';
8+
9+
describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
10+
describe('Behavior: "Vitest mocking unsupported"', () => {
11+
beforeEach(() => {
12+
setupApplicationTarget(harness);
13+
});
14+
15+
it('should fail when vi.mock is used', async () => {
16+
harness.useTarget('test', {
17+
...BASE_OPTIONS,
18+
});
19+
20+
harness.writeFile(
21+
'src/app/mock-throw.spec.ts',
22+
`
23+
import { vi } from 'vitest';
24+
vi.mock('./something', () => ({}));
25+
`,
26+
);
27+
28+
// Overwrite default to avoid noise
29+
harness.writeFile(
30+
'src/app/app.component.spec.ts',
31+
`
32+
import { describe, it, expect } from 'vitest';
33+
describe('Ignored', () => { it('pass', () => expect(true).toBe(true)); });
34+
`,
35+
);
36+
37+
const { result, logs } = await harness.executeOnce();
38+
expect(result?.success).toBeFalse();
39+
});
40+
});
41+
});

0 commit comments

Comments
 (0)