GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — unit-test (vitest): isolate:false default silently breaks cross-file vi.mock() of shared modules on low-CPU workers · Issue #33570 · angular/angular-cli

unit-test (vitest): isolate:false default silently breaks cross-file vi.mock() of shared modules on low-CPU workers #33570

New issue
Copy link
New issue
Copy link
Closed
Closed
unit-test (vitest): isolate:false default silently breaks cross-file vi.mock() of shared modules on low-CPU workers#33570
Copy link
Labels
area: @angular/buildgemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini
Milestone
needsTriage

Description

@rommelandrea
rommelandrea
opened on Jul 15, 2026
Issue body actions

Which @angular/* package(s) are the source of the bug?

build

Is this a regression?

No

Description

@angular/build:unit-test (Vitest runner) sets test.isolate: false by default (to match the historical Karma/Jasmine experience — src/builders/unit-test/runners/vitest/plugins.js, and again in executor.js). Combined with the builder's pre-bundled test entry points that share esbuild chunks, this silently breaks vi.mock() of a bare (node_modules) module across spec files that land on the same worker.

Mechanism

  1. Each spec is pre-bundled by esbuild. A component/service imported by more than one spec is hoisted into a shared chunk (chunk-*.js), and third-party packages are kept external (externalPackages: true).
  2. With isolate: false, Vitest reuses a worker (and its module registry) across spec files.
  3. Spec A and spec B both vi.mock('some-pkg', factory) (different factories) and both import the same shared component that imports some-pkg. Whichever spec runs first on a worker instantiates the shared chunk bound to its mock. When the other spec later runs on the same worker, the cached shared-chunk module is reused, so its own vi.mock('some-pkg') never intercepts the component's import — its mock handles stay empty while the component silently runs against the first spec's mock (or the real module).

Because Vitest's worker count follows os.availableParallelism(), the failure is CPU-count dependent: green on high-core dev machines / CI, but deterministically red on low-core CI runners (e.g. a 2-vCPU Kubernetes/ARC pod), where many spec files share each worker in a stable, size-ordered sequence. This makes it a nasty "works on my machine / fails only on constrained CI" trap.

Minimal reproduction

Conditions (no third-party lib needed — any bare module works):

  • pkg — a trivial node_modules package exporting a factory function, e.g. makeThing().
  • SharedCmp — a standalone component that imports pkg and calls makeThing() in ngAfterViewInit.
  • a.spec.ts — vi.mock('pkg', () => ({ makeThing: () => handlesA })), imports SharedCmp (e.g. via a page that renders it), asserts against handlesA.
  • b.spec.ts — vi.mock('pkg', () => ({ makeThing: () => handlesB })), creates SharedCmp directly, asserts against handlesB.

Run ng test with the two specs while constraining workers so both files share one worker (e.g. on a 2-CPU machine, or VITEST_MAX_THREADS=1), with a.spec.ts scheduled before b.spec.ts. b.spec.ts's assertions fail because SharedCmp is bound to handlesA (or the real module), and handlesB is never called. Adding a project vitest-base.config.ts with test: { isolate: true } (via "runnerConfig": true) makes both pass.

Exception or Error

No thrown error — the component runs against the wrong (foreign) mock instance.
Symptom: the later spec's own mock handles are never invoked
(e.g. "expected 1, got 0" / "undefined is not a spy"), only on low-CPU workers.

Your Environment

@angular/build: 21.2.12
vitest: 4.1.7
Node: 24.15.0
Runner: vitest (unit-test builder), jsdom environment
Repro trigger: low worker count (2-vCPU CI runner) — deterministic;
high-core machines mask it.

Anything else relevant?

Workaround (works): opt into an external runner config and re-enable isolation —

angular.json:

"test": {
  "builder": "@angular/build:unit-test",
  "options": { "runnerConfig": true }
}

vitest-base.config.ts:

import { defineConfig } from 'vitest/config';
export default defineConfig({ test: { isolate: true } });

Suggested fixes for consideration:

  • Document prominently that vi.mock() of shared/bare modules is unsafe under the default isolate: false (the current in-repo guard only rejects relative vi.mock paths — angular:vitest-mock-patch — but bare-specifier mocks are the ones that break cross-file).
  • Consider detecting vi.mock usage and warning (or auto-isolating) when specs share chunks, or make isolation the default for the Vitest runner.

Happy to package a StackBlitz/minimal repo if that would help triage.

Activity

  1. angular-robot
    added
    area: @angular/build
    gemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini
    on Jul 15, 2026
  2. angular-robot
    added this to the needsTriage milestone on Jul 15, 2026
  3. clydin commented on Aug 13, 2026

    @clydin
    clydin
    on Aug 13, 2026
    Member
    More actions

    Resolved via #33729

  4. clydin
    closed this as completedon Aug 13, 2026
  5. angular-automatic-lock-bot commented on Oct 19, 2026

    @angular-automatic-lock-bot
    angular-automatic-lock-botBot
    on Oct 19, 2026
    More actions

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  6. angular-automatic-lock-bot
    locked and limited conversation to collaborators on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: @angular/buildgemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

      Milestone

      • needsTriageNo due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.