ngneat/spectator

ng 19 and default standalone and ng-mocks => `assertNoStandaloneComponents()` fails

Opened this issue · 1 comments

Is this a regression?

Yes

Description

This is a regression, but it's not due to a bug in Spectator - it's caused by a breaking change in ng19 and a bug in ng-mocks that doesn't yet handle that change. This issue was brought up in #682 - it is not related to the fix for #682, but should be documented as a form of pro-active support b/c it's likely that people will report it here.

In short, if a test uses spectator, and ng 19, and ng-mocks, and the test uses a default standalone component (standalone: is not declared but the component is standalone by default), createComponentFactory() fails with an error:

Failed: Unexpected "MyComponent" found in the "declarations" array of the "TestBed.configureTestingModule" call, "MyComponent" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?

This error is caused by a bug in ng-mocks: ng-mocks#10632 - I don't believe anything needs to be fixed in spectator, but I can't verify that in tests until there's a fix for the ng-mocks bug.

There is an easy workaround - standalone components used in tests with mocks need to have standalone: true explicitly declared.

Please provide a link to a minimal reproduction of the bug

https://github.com/johncrim/spectator/blob/bug/default-standalone-mocks-failure/projects/spectator/test/standalone/component/container-with-mock.spec.ts

Please provide the exception or error you saw

✔ Browser application bundle generation complete.
Chrome 131.0.0.0 (Windows 10) Standalone component with mock using Spectator should render mocked and un-mocked children FAILED
        Failed: Unexpected "MockOfChildBComponent" found in the "declarations" array of the "TestBed.configureTestingModule" call, "MockOfChildBComponent" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?
            at forEach (node_modules/@angular/core/fesm2022/testing.mjs:769:23)
            at Array.forEach (<anonymous>)
            at assertNoStandaloneComponents (node_modules/@angular/core/fesm2022/testing.mjs:765:11)
            at TestBedCompiler.configureTestingModule (node_modules/@angular/core/fesm2022/testing.mjs:844:13)
            at TestBedImpl.call (node_modules/@angular/core/fesm2022/testing.mjs:1974:23)
            at TestBedImpl.configureTestingModule (node_modules/ng-mocks/index.mjs:1:109087)
            at Function.call (node_modules/@angular/core/fesm2022/testing.mjs:1789:37)
            at Function.call (node_modules/ng-mocks/index.mjs:1:128562)
            at Function.configureTestingModule (node_modules/ng-mocks/index.mjs:1:109277)
            at UserContext.<anonymous> (projects/spectator/src/lib/spectator/create-factory.ts:129:13)

Please provide the environment you discovered this bug in

Windows 11
Angular 19.01
"@ngneat/spectator": "^19.1.2",
"ng-mocks": "^14.13.1",

Anything else?

This test case reproduces the error:

import { Component } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { MockComponent } from 'ng-mocks';

@Component({
  selector: `test-child-a`,
  template: `<h3 id="child-a">child A</h3>`,
  // standalone: true,
})
export class ChildAComponent {}

@Component({
  selector: `test-child-b`,
  template: `<h3 id="child-b">child B</h3>`,
  // standalone: true,
})
export class ChildBComponent {}

@Component({
  selector: `test-container`,
  template: `<test-child-a /><test-child-b />`,
  // standalone: true,
  imports: [ChildAComponent, ChildBComponent],
})
export class ContainerComponent {}

fdescribe('Standalone component with mock', () => {
  describe('using Spectator', () => {
    let spectator: Spectator<ContainerComponent>;

    const createComponent = createComponentFactory({
      component: ContainerComponent,
      imports: [
        ChildAComponent,
        // ChildBComponent // Removing the mock and declaring the component here fixes the create error
        MockComponent(ChildBComponent),
      ],
    });

    beforeEach(() => {
      spectator = createComponent();
    });

    it('should render mocked and un-mocked children', () => {
      expect(spectator.query('#child-a')).toContainText('child A');
      expect(spectator.query(ChildBComponent)).toBeTruthy();
      expect(spectator.query('#child-b')).not.toContainText('child B');
    });
  });
});

If you remove the MockComponent line and declare ChildBComponent as an import, the failure goes away (but the test fails).

If you uncomment all the standalone: true lines, the test succeeds. That's the easy but non-obvious workaround for this.

Do you want to create a pull request?

Sure - I am willing to improve these tests and provide a PR with the tests after the ng-mocks bug is fixed. It would require adding ng-mocks as a devDependency, which is probably not a bad idea IMO.

@carflynn2009 and @PeterEnevoldsen - this is the issue you brought up in #682. I came across the same issue today, so I investigated it and created a repro case.