From 3d7f8ab60ba210557a3c5dedd1bdf4908a317969 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Sun, 9 Nov 2025 09:16:50 -0500 Subject: [PATCH] fix(@angular/build): ensure TestBed setup is robust in non-isolated Vitest This commit refines the Angular TestBed initialization within the Vitest runner to ensure correct behavior in non-isolated test environments (`isolate: false`). Previously, in a non-isolated setup, the TestBed initialization logic in the virtual setup file could be problematic if the file was evaluated multiple times. This could lead to re-registering `beforeEach`/`afterEach` hooks or attempting to re-initialize the TestBed, causing errors or unexpected test behavior. This change introduces a globally unique symbol guard (`Symbol.for('@angular/cli/testbed-setup')`) that ensures the entire TestBed setup block (including hook registration and `initTestEnvironment`) is executed only once per test run. This prevents redundant executions and potential errors. Finally, the `isolate` option in Vitest is now explicitly defaulted to `false` when not specified by the user, aligning with the traditional Karma/Jasmine experience. --- .../unit-test/runners/vitest/build-options.ts | 34 +++++++++++++------ .../unit-test/runners/vitest/executor.ts | 5 ++- .../unit-test/runners/vitest/plugins.ts | 2 ++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts index 5df9aafe0d57..19143728afe8 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts @@ -49,17 +49,29 @@ function createTestBedInitVirtualFile( import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing'; import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; ${providersImport} - // Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/srcs/test_hooks.ts#L21-L29 - beforeEach(getCleanupHook(false)); - afterEach(getCleanupHook(true)); - @NgModule({ - providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}...providers], - }) - export class TestModule {} - getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), { - errorOnUnknownElements: true, - errorOnUnknownProperties: true, - }); + + const ANGULAR_TESTBED_SETUP = Symbol.for('@angular/cli/testbed-setup'); + if (!globalThis[ANGULAR_TESTBED_SETUP]) { + globalThis[ANGULAR_TESTBED_SETUP] = true; + + // The Angular TestBed needs to be initialized before any tests are run. + // In a non-isolated environment, this setup file can be executed multiple times. + // The guard condition above ensures that the setup is only performed once. + + // Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/srcs/test_hooks.ts#L21-L29 + beforeEach(getCleanupHook(false)); + afterEach(getCleanupHook(true)); + + @NgModule({ + providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}...providers], + }) + class TestModule {} + + getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), { + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }); + } `; } diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index 950a96f2adac..b09e2ec5e3ca 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -223,7 +223,10 @@ export class VitestExecutor implements TestExecutor { reporters, setupFiles: testSetupFiles, projectPlugins, - include: [...this.testFileToEntryPoint.keys()], + include: [...this.testFileToEntryPoint.keys()].filter( + // Filter internal entries + (entry) => !entry.startsWith('angular:'), + ), }), ], }, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index e425bdf95e4d..1a07e13ad955 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -125,6 +125,8 @@ export function createVitestConfigPlugin(options: VitestConfigPluginOptions): Vi setupFiles: combinedSetupFiles, include, globals: testConfig?.globals ?? true, + // Default to `false` to align with the Karma/Jasmine experience. + isolate: testConfig?.isolate ?? false, ...(browser ? { browser } : {}), // If the user has not specified an environment, use a smart default. ...(!testConfig?.environment