Skip to content

TestBed.overrideComponent breaks module imports with @angular/build:unit-test #33064

Description

@m-yst-ery

Command

test

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

No response

Description

Reopening from angular/angular#68363 as I don't feel it was fully answered and I later found the issue is related to the new builder.

When using the @angular/build:unit-test builder, TestBed.overrideComponent breaks imports out of modules. This happens for both vitest and karma runners, so I would assume it is connected to the build, not the test execution.

The issue does not occur when using other builder like @angular-devkit/build-angular:karma

Minimal Reproduction

Plain Angular project + packages needed for running karma

// app.spec.ts
import { Component, NgModule } from '@angular/core';
import { TestBed } from '@angular/core/testing';

@Component({
  selector: 'test-component',
  template: '',
})
export class TestComponent {}

@NgModule({
  imports: [TestComponent],
  exports: [TestComponent],
})
export class TestModule {}

@Component({
  selector: 'app-root',
  imports: [TestModule],
  template: ` <test-component></test-component> `,
})
export class App {}

describe('App', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [App],
    })
      .overrideComponent(App, {}) // <= problematic call
      .compileComponents();
  });

  it('should create', () => {
    expect(TestBed.createComponent(App)).toBeTruthy();
  });
});
// angular.json
...
// fails
"test-vitest": {
  "builder": "@angular/build:unit-test",
  "options": {
    "runner": "vitest"
  }
},
// fails
"test-karma": {
  "builder": "@angular/build:unit-test",
  "options": {
    "runner": "karma"
  }
},
// this one works
"test-karma-devkit": {
  "builder": "@angular-devkit/build-angular:karma",
  "options": {
    "tsConfig": "./tsconfig.spec.json"
  }
}
...

Running the test with @angular/build:unit-test it fails for both runners with

Error: NG0304: 'test-component' is not a known element (used in the '_App' component template):
1. If 'test-component' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2. If 'test-component' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.
 ❯ validateElementIsKnown ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/element_validation.ts:116:14
 ❯ validateElementIsKnown ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/element.ts:151:4
 ❯ initializeElement ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/element.ts:127:2
 ❯ ɵɵelementStart ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/element.ts:218:2
 ❯ _App_Template ng:/_App.js:6:21
 ❯ templateFn ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/shared.ts:109:4
 ❯ executeTemplate ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/render.ts:107:6
 ❯ renderView ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/render.ts:47:4
 ❯ renderComponent ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/render.ts:160:4
 ❯ renderChildComponents ../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/packages/core/src/render3/instructions/render.ts:140:6

With the @angular-devkit/build-angular:karma it passes as expected.

Also actually overriding something in TestBed.overrideComponent doesn't change the behaviour. I originally discovered this with a test that needs to stub a component. I left it out for simplicity here.

Exception or Error


Your Environment

_                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI       : 21.2.8
Angular           : 21.2.10
Node.js           : 24.15.0
Package Manager   : npm 10.9.4
Operating System  : win32 x64

┌───────────────────────────────┬───────────────────┬───────────────────┐
│ Package                       │ Installed Version │ Requested Version │
├───────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/build-angular │ 21.2.8            │ ^21.2.8           │
│ @angular/build                │ 21.2.8            │ ^21.2.7           │
│ @angular/cli                  │ 21.2.8            │ ^21.2.7           │
│ @angular/common               │ 21.2.10           │ ^21.2.0           │
│ @angular/compiler             │ 21.2.10           │ ^21.2.0           │
│ @angular/compiler-cli         │ 21.2.10           │ ^21.2.0           │
│ @angular/core                 │ 21.2.10           │ ^21.2.0           │
│ @angular/forms                │ 21.2.10           │ ^21.2.0           │
│ @angular/platform-browser     │ 21.2.10           │ ^21.2.0           │
│ @angular/router               │ 21.2.10           │ ^21.2.0           │
│ rxjs                          │ 7.8.2             │ ~7.8.0            │
│ typescript                    │ 5.9.3             │ ~5.9.2            │
│ vitest                        │ 4.1.5             │ ^4.0.8            │
└───────────────────────────────┴───────────────────┴───────────────────┘

Anything else relevant?

No response

Activity

  1. abercrave commented on May 29, 2026

    @abercrave

    Confirming this happens at scale in a real-world Angular 21 codebase doing a Jest → Vitest migration via @angular/build:unit-test, and reinforcing the OP's clarification that this is distinct from #48432 — that's an older issue from Angular 15 (2022) under the legacy @angular-devkit/build-angular builder, with a different trigger (set: { imports: [...] } vs empty {}) and a different effect (override silently ignored vs existing imports destroyed). Our independent reproduction matches the OP's: the bug only manifests under @angular/build:unit-test; the legacy builder is unaffected. ~49 specs out of ~1,225 (~4%) in our codebase hit this exact pattern, concentrated in detail-form components where one shared NgModule re-exports standalone components used across a feature.

    Test-only workarounds that don't work:

    • overrideComponent({ add: { imports: [TheReExportedComponent] } }) — the SUT template is AOT-pre-compiled against the SUT's original imports; runtime overrideComponent modifications don't reach element resolution, so the NG0304 still fires.
    • CUSTOM_ELEMENTS_SCHEMA — silences the error but leaves the element unrendered, breaking any test that asserts on rendered output.

    Workaround that does work (production code change):

    1. Remove the NgModule from the SUT's imports: [...] and replace with direct standalone-component imports for the declarations the template actually uses.
    2. Move any module-level providers from the NgModule's providers: [...] to the SUT's own @Component.providers.

    Mechanical per-SUT. Verified on one SUT in our codebase: the NG0304 error is gone, and the Jest sibling continues to pass (the SUT change is purely additive).

    Happy to provide an isolated repro of either the failure or the workaround if it would help triage. Thanks for the original report — the detail that an empty overrideComponent(App, {}) triggers the bug saved us hours.

    My Environment

         _                      _                 ____ _     ___
        / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
       / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
      / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
     /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                    |___/
    
    
    Angular CLI       : 21.2.8
    Angular           : 21.2.9
    Node.js           : 24.14.0
    Package Manager   : npm 11.15.0
    Operating System  : darwin arm64
    
    ┌───────────────────────────────────┬───────────────────┬───────────────────┐
    │ Package                           │ Installed Version │ Requested Version │
    ├───────────────────────────────────┼───────────────────┼───────────────────┤
    │ @angular-devkit/build-angular     │ 21.2.8            │ 21.2.8            │
    │ @angular-devkit/core              │ 21.2.8            │ 21.2.8            │
    │ @angular/animations               │ 21.2.9            │ 21.2.9            │
    │ @angular/build                    │ 21.2.8            │ 21.2.8            │
    │ @angular/cdk                      │ 21.2.8            │ 21.2.8            │
    │ @angular/cli                      │ 21.2.8            │ 21.2.8            │
    │ @angular/common                   │ 21.2.9            │ 21.2.9            │
    │ @angular/compiler                 │ 21.2.9            │ 21.2.9            │
    │ @angular/compiler-cli             │ 21.2.9            │ 21.2.9            │
    │ @angular/core                     │ 21.2.9            │ 21.2.9            │
    │ @angular/forms                    │ 21.2.9            │ 21.2.9            │
    │ @angular/language-service         │ 21.2.9            │ 21.2.9            │
    │ @angular/platform-browser         │ 21.2.9            │ 21.2.9            │
    │ @angular/platform-browser-dynamic │ 21.2.9            │ 21.2.9            │
    │ @angular/router                   │ 21.2.9            │ 21.2.9            │
    │ @angular/service-worker           │ 21.2.9            │ 21.2.9            │
    │ rxjs                              │ 7.8.2             │ ^7.8.2            │
    │ typescript                        │ 5.9.3             │ ^5.9.3            │
    │ vitest                            │ 4.1.7             │ ^4.1.7            │
    │ zone.js                           │ 0.16.1            │ 0.16.1            │
    └───────────────────────────────────┴───────────────────┴───────────────────┘
    
  2. johncrim commented on Jul 4, 2026

    @johncrim

    We're affected by this in a big way - it's making migrating to vitest/the new builder much more work (and much more verbose) than it should be. Initially I thought it was a bug in @testing-library/angular, but while working on a repro case, I found that it's a surprising bug in TestBed. Common testing scenarios like overriding the test component template or adding/updating imports of test component dependencies from modules are all broken. I can provide more repro cases if it's helpful.

    This is a severe bug that will add a bunch of frustration to anyone migrating to the new test builder.

  3. johncrim commented on Jul 5, 2026

    @johncrim

    My take is that this bug is a bigger problem than the Angular team perceives it to be (I'm guessing that Google isn't using the new @angular/build:unit-test builder, otherwise this would get more attention), so I added some test cases to show what works/doesn't work in a minimal repro repo:

    https://github.com/johncrim/ng-testbed-compile-bug

    Key points:

    1. Bug is still present and behavior unchanged in ng 22 (same behavior as ng 21). It is not specific to standalone components like TestBed.overrideComponent doesn't update standalone components angular#48432 .
    2. These tests work as expected in the legacy devkit/karma builder, which is deprecated. This bug should be counted as a regression, assuming that the new unit test builder should support the same test code as the legacy builder.
    3. These failures prevent easy migration (to the new test builder) of tests that use test helpers like @testing-library/angular's render(template string).
    4. The presence of these bugs will likely add a lot of unnecessary work and frustration to teams migrating large numbers of tests to the new builder. The error messages are misleading and cause of failure difficult to determine.
    5. In my experience this bug also confuses AI agents / results in a lot of cycles and trying things when using AI to migrate tests to vitest/the new builder
  4. johncrim commented on Jul 5, 2026

    @johncrim

    I'm pretty sure this compile failure is related to this bug, though it may be separate. This code block compiles and runs successfully in the devkit/karma builder, but fails to build with the unit-test builder:

      // Does not compile using `@angular/build:unit-test` builder, so first test can't be run
      // When running using unit-test builder, comment out this component and the first test.
      @Component({
        template: `<div data-testid="test-host"><test-cmp1/></div>`,
        standalone: false
      })
      class TestHostComponent { }
    
      it('creates host component with child component from module', async () => {
        const testBed = TestBed.configureTestingModule({
          declarations: [TestHostComponent],
          imports: [TestModule]
        });
        await testBed.compileComponents();
    
        const fixture = testBed.createComponent(TestHostComponent);
    
        await expectContainsTestCmp1(fixture);
      });

    The compile failure when using the unit-test builder is:

    Application bundle generation failed. [1.551 seconds] - 2026-07-05T20:41:34.238Z
    
    X [ERROR] NG8001: 'test-cmp1' is not a known element:
    1. If 'test-cmp1' is an Angular component, then verify that it is part of this module.
    2. If 'test-cmp1' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. [plugin angular-compiler]
    
        src/tests/testbedNotStandaloneOverrideImports.spec.ts:31:44:
          31 │     template: `<div data-testid="test-host"><test-cmp1/></div>`,
             ╵                                             ~~~~~~~~~~~~
    

    Since it's a compile failure, none of the tests run unless the block is commented out.

  5. JeanMeche commented on Jul 5, 2026

    @JeanMeche
    Member

    The core issue here is that the vite test runner builds unit tests with AOT instead of JIT (which was what the karma runner was doing).

    If you set

     "test": {
              "builder": "@angular/build:karma",
              "options": {
                "aot": true,
    ...
    

    You will get the same error.

    The issues described here as closely related to angular/angular#65665.

  6. johncrim commented on Jul 6, 2026

    @johncrim

    Thanks for the comment @JeanMeche - I checked and verified that you're correct, that the compile failure I'm seeing with the unit-test builder can be reproduced with the @angular-devkit/build-angular:karma builder by adding "aot": true.

    I find this kind of alarming, since it's a valid test that just doesn't work when AOT is enabled. But reading the thread in angular#65665 does help clear things up for the compile error. In short, the AOT compiler requires a separate NgModule to discover the dependencies that can be used in the non-standalone test component's template. The testing module in configureTestingModule() can't be used as the module when AOT is enabled.

    Conclusion: I thought the compile error was a related or the same bug - but it is separate, and already covered by angular#65665.

    If I fix that, and run the tests in the legacy devkit/karma builder with aot=true, I get some overlapping and some different failures:

    Overriding a test component template succeeds in the legacy test builder and FAILs in the new one:

        TestBed.configureTestingModule({
          imports: [
            TestHostComponent
          ]
        })
          .overrideComponent(TestHostComponent, {
            set: {
              template: `<test-cmp1/><test-cmp1/>`,
            }
          });
      });

    Per the description of this bug above, calling overrideComponent() breaks test component imports.

    Importing a standalone child component works in the new test builder and fails in the legacy one:

       TestBed.configureTestingModule({
            imports: [
              StandaloneWrapperComponent
            ]
          })
          .overrideComponent(StandaloneWrapperComponent, {
            set: {
              template: `<test-cmp1/>`,
              imports: [TestCmp1]
            }
          });

    (which is good, b/c it's a critical workaround)

    And importing test component dependencies via modules only works with JIT compilation (the legacy default) and fails in the legacy builder with aot=true, and fails in the new builder:

    TestBed.configureTestingModule({
          imports: [
            StandaloneWrapperComponent
          ]
        }).overrideComponent(StandaloneWrapperComponent, {
          set: {
            template: `<test-cmp1/>`,
            imports: [TestModule]
          }
        });

    All of these cases can be examined and run in my https://github.com/johncrim/ng-testbed-compile-bug test case repo.

    I stand by my previous statement that these changes in behavior are a big deal for anyone migrating a large number of tests to the new test builder - especially since the old builder is deprecated so teams trying to stay on the Angular upgrade path are being pushed to migrate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions