Skip to content

Commit ce57763

Browse files
committed
fix(@angular/build): ensure @angular/compiler is loaded for Vitest TestBed initialization
Provide a dedicated setup file to import '@angular/compiler' prior to TestBed initialization. This ensures the compiler facade is published before 'init-testbed.js' or any shared code-split chunks (such as those configured via 'providersFile') are evaluated, avoiding JIT compilation errors when injectable dependencies are resolved during test initialization.
1 parent dd68d3a commit ce57763

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ export async function getVitestBuildOptions(
230230
}
231231
}
232232

233+
// The Angular compiler facade must be loaded in a dedicated setup file before TestBed initialization.
234+
// This ensures the compiler facade is published before 'init-testbed' or any shared code-split chunks
235+
// (such as those from a configured 'providersFile') are evaluated.
236+
entryPoints.set('init-compiler', 'angular:compiler-init');
233237
entryPoints.set('init-testbed', 'angular:test-bed-init');
234238
entryPoints.set('vitest-mock-patch', 'angular:vitest-mock-patch');
235239

@@ -326,6 +330,7 @@ export async function getVitestBuildOptions(
326330
return {
327331
buildOptions,
328332
virtualFiles: {
333+
'angular:compiler-init': "import '@angular/compiler';",
329334
'angular:test-bed-init': testBedInitContents,
330335
'angular:vitest-mock-patch': mockPatchContents,
331336
},

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,12 @@ export class VitestExecutor implements TestExecutor {
243243
private prepareSetupFiles(): string[] {
244244
const { setupFiles } = this.options;
245245
// Add setup file entries for TestBed initialization and project polyfills
246-
const testSetupFiles = ['init-testbed.js', 'vitest-mock-patch.js', ...setupFiles];
246+
const testSetupFiles = [
247+
'init-compiler.js',
248+
'init-testbed.js',
249+
'vitest-mock-patch.js',
250+
...setupFiles,
251+
];
247252

248253
// TODO: Provide additional result metadata to avoid needing to extract based on filename
249254
if (this.buildResultFiles.has('polyfills.js')) {

‎packages/angular/build/src/builders/unit-test/tests/options/providers-file_spec.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,59 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
6060
const { result } = await harness.executeOnce();
6161
expect(result?.success).toBeTrue();
6262
});
63+
64+
it('should work with providers that inject services requiring JIT compilation', async () => {
65+
await harness.writeFiles({
66+
'src/test.service.ts': `
67+
import { Injectable, inject } from '@angular/core';
68+
import { Router } from '@angular/router';
69+
70+
@Injectable({ providedIn: 'root' })
71+
export class TestService {
72+
router = inject(Router);
73+
}
74+
`,
75+
'src/test.providers.ts': `
76+
import { TestService } from './test.service';
77+
export default [TestService];
78+
`,
79+
'src/app/app.component.spec.ts': `
80+
import { TestBed } from '@angular/core/testing';
81+
import { AppComponent } from './app.component';
82+
import { TestService } from '../test.service';
83+
import { describe, expect, it } from 'vitest';
84+
85+
describe('AppComponent', () => {
86+
it('should create the app and inject TestService', () => {
87+
TestBed.configureTestingModule({
88+
declarations: [AppComponent],
89+
});
90+
const fixture = TestBed.createComponent(AppComponent);
91+
const app = fixture.componentInstance;
92+
const testService = TestBed.inject(TestService);
93+
expect(app).toBeTruthy();
94+
expect(testService).toBeTruthy();
95+
expect(testService.router).toBeTruthy();
96+
});
97+
});
98+
`,
99+
});
100+
101+
await harness.modifyFile('src/tsconfig.spec.json', (content) => {
102+
const tsConfig = JSON.parse(content);
103+
tsConfig.files ??= [];
104+
tsConfig.files.push('test.service.ts', 'test.providers.ts');
105+
106+
return JSON.stringify(tsConfig);
107+
});
108+
109+
harness.useTarget('test', {
110+
...BASE_OPTIONS,
111+
providersFile: 'src/test.providers.ts',
112+
});
113+
114+
const { result } = await harness.executeOnce();
115+
expect(result?.success).toBeTrue();
116+
});
63117
});
64118
});

0 commit comments

Comments
 (0)