`createHostFactory` with `declareComponent: false` can't find tested component in the host with Vitest test runner
Is this a regression?
No
Description
I have a module ButtonModule with a single non-standalone ButtonComponent exported from it.
As a unit tests runner I use Angular's experimental @angular/build:unit-test builder with the following configuration:
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runner": "vitest",
"browsers": [
"chromium"
],
"buildTarget": "::development",
"tsConfig": "tsconfig.spec.json",
"codeCoverage": false
}
}
In the ButtonComponent's spec there is the following code:
import {ButtonComponent} from './button.component';
import {
createHostFactory,
SpectatorHost
} from '@ngneat/spectator/vitest';
import {ButtonModule} from './button-module';
describe('ButtonComponent', () => {
let spectator: SpectatorHost<ButtonComponent>;
const createComponentHost = createHostFactory({
component: ButtonComponent,
imports: [ButtonModule],
declareComponent: false
});
it('should create', () => {
spectator = createComponentHost('<app-button>');
expect(spectator.query('button')).toExist();
});
});
Note declareComponent: false option.
Running npm test leads to the following runtime error:
Error: Cannot find component/directive class _ButtonComponent {
static \u0275fac = function ButtonComponent_Factory(__ngFactoryType__) {
return new (__ngFactoryType__ || _ButtonComponent)();
};
static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ButtonComponent, selectors: [["app-button"]], standalone: false, ngContentSelectors: _c0, decls: 2, vars: 0, template: function ButtonComponent_Template(rf, ctx) {
if (rf & 1) {
\u0275\u0275projectionDef();
\u0275\u0275elementStart(0, "button");
\u0275\u0275projection(1);
\u0275\u0275elementEnd();
}
}, encapsulation: 2 });
} in host template 😔
Changing the test config to the default one based on Karma (see below) and changing the import in button.component.spec.ts from @ngneat/spectator/vitest to @ngneat/spectator fixes the problem and the test passes:
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
]
}
}
In the reproduction repo run npm i && npm test and you'll see the error.
Please provide a link to a minimal reproduction of the bug
https://github.com/th0r/ng-neat-vitest-host-component-bug
Please provide the exception or error you saw
Error: Cannot find component/directive class _ButtonComponent {
static \u0275fac = function ButtonComponent_Factory(__ngFactoryType__) {
return new (__ngFactoryType__ || _ButtonComponent)();
};
static \u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ButtonComponent, selectors: [["app-button"]], standalone: false, ngContentSelectors: _c0, decls: 2, vars: 0, template: function ButtonComponent_Template(rf, ctx) {
if (rf & 1) {
\u0275\u0275projectionDef();
\u0275\u0275elementStart(0, "button");
\u0275\u0275projection(1);
\u0275\u0275elementEnd();
}
}, encapsulation: 2 });
} in host template 😔
Please provide the environment you discovered this bug in
Angular CLI: 20.2.2
Node: 22.16.0
Package Manager: npm 10.9.3
OS: darwin arm64
Angular: 20.2.4
... common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router
Package Version
------------------------------------
@angular-devkit/architect 0.2002.2
@angular-devkit/core 20.2.2
@angular-devkit/schematics 20.2.2
@angular/build 20.2.2
@angular/cli 20.2.2
@schematics/angular 20.2.2
rxjs 7.8.2
typescript 5.9.2
zone.js 0.15.1
Anything else?
No response
Do you want to create a pull request?
No
@chimurai could you take a look please? It looks like <app-button> component is not registered for some reason as in the Vitest console I see the following message:
NG0304: 'app-button' is not a known element (used in the '_HostComponent' component template):
1. If 'app-button' is an Angular component, then verify that it is a part of an @NgModule where this component is declared.
2. If 'app-button' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
Isn't your case in the same purpose described in Testing Module in the docs? If so, why are you using createHostFactory instead of createComponentFactory?
What do you mean by my case? I've just created a smallest example to show the bug in createHostFactory.
I meant you're not using it as a host, and not even with the options presented at Testing With Host, although declareComponent do exists as one (because SpectatorHostOptions extends SpectatorOptions type).
@KlausEverWalkingDev what options? hostProps? All those options are optional as I may want to just test that my component correctly projects the content I passed to it e.g.
spectator = createHost(`<app-button>Some content</app-button>`);
expect(spectator.hostElement).toHaveText('Some content');
But all of this doesn't matter as what I tried to show in the spec example is that the line spectator = createComponentHost('<app-button>') immediately throws an error as the code in the createComponentHost function can't locate the instance of the tested component. So even if it would be something like
createComponentHost('<app-button>Some content</app-button>')
it would immediately fail anyway.
What I'm failing to understand is why are you passing declareComponent when in the it's nowhere to be found when you testing with host.
If you wanted to test the module though:
That option just makes sense when you test modules, where you see you have to use createComponentFactory and not createComponentHost or createHostFactory.
Because it's not a standalone component. In my example the tested component (app-button) doesn't have any dependencies, but in the real world use case it may use tens of dependent components/directives/pipes which you would have to declare manually in createHostFactory. So there is a way to avoid that - you can just import the whole module, which exports the tested component, and declareComponent in this case says that there is no need to declare (register) the tested component again, as it's already declared (exported from the module).
@NetanelBasal @chimurai any feedback guys?