components
components copied to clipboard
bug(Dialog): MatTestDialogOpener (This constructor is not compatible with Angular Dependency Injection)
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
The MatDialog API mentions a MatTestDialogOpener
that can be used when writing tests that involve a mat-dialog.
Instead, when using the MatTestDialogOpener
when writing my test it throws an error:
NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
Reproduction
I don't know how to provide a StackBlitz reproduction that involve a test. Using the mat dialog opener test I was still able to reproduce the error (In the hopes of eliminating user error 🙂)
import {Component, Inject} from '@angular/core';
import {fakeAsync, TestBed, flush} from '@angular/core/testing';
import {MatTestDialogOpenerModule, MatTestDialogOpener} from '@angular/material/dialog/testing';
import {MAT_DIALOG_DATA, MatDialogRef, MatDialogState} from '@angular/material/dialog';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
describe('MDC-based MatTestDialogOpener', () => {
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [MatTestDialogOpenerModule, NoopAnimationsModule],
declarations: [ExampleComponent],
});
TestBed.compileComponents();
}));
it('should open a dialog when created', fakeAsync(() => {
const fixture = TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent));
flush();
expect(fixture.componentInstance.dialogRef.getState()).toBe(MatDialogState.OPEN);
expect(document.querySelector('mat-dialog-container')).toBeTruthy();
}));
it('should throw an error if no dialog component is provided', () => {
expect(() => TestBed.createComponent(MatTestDialogOpener)).toThrow(
Error('MatTestDialogOpener does not have a component provided.'),
);
});
it('should pass data to the component', fakeAsync(() => {
const config = {data: 'test'};
TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent, config));
flush();
const dialogContainer = document.querySelector('mat-dialog-container');
expect(dialogContainer!.innerHTML).toContain('Data: test');
}));
it('should get closed result data', fakeAsync(() => {
const config = {data: 'test'};
const fixture = TestBed.createComponent(
MatTestDialogOpener.withComponent<ExampleComponent, ExampleDialogResult>(
ExampleComponent,
config,
),
);
flush();
const closeButton = document.querySelector('#close-btn') as HTMLElement;
closeButton.click();
flush();
expect(fixture.componentInstance.closedResult).toEqual({reason: 'closed'});
}));
});
interface ExampleDialogResult {
reason: string;
}
/** Simple component for testing MatTestDialogOpener. */
@Component({
template: `
Data: {{data}}
<button id="close-btn" (click)="close()">Close</button>
`,
})
class ExampleComponent {
constructor(
public dialogRef: MatDialogRef<ExampleComponent, ExampleDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {}
close() {
this.dialogRef.close({reason: 'closed'});
}
}
Expected Behavior
To be able to write tests using the MatTestDialogOpener
Actual Behavior
MatTestDialogOpener
throwing an error related to the Dependency injection
NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
at ɵɵinvalidFactoryDep (../../../../node_modules/@angular/core/fesm2022/core.mjs:668:11)
at NodeInjectorFactory.MatTestDialogOpener2_Factory [as factory] (../../../../ng:/MatTestDialogOpener2/ɵfac.js:5:48)
at getNodeInjectable (../../../../node_modules/@angular/core/fesm2022/core.mjs:4669:44)
at createRootComponent (../../../../node_modules/@angular/core/fesm2022/core.mjs:13264:35)
at ComponentFactory.create (../../../../node_modules/@angular/core/fesm2022/core.mjs:13122:25)
at initComponent (../../../../node_modules/@angular/core/fesm2022/testing.mjs:26421:51)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:421:30)
at FakeAsyncTestZoneSpec.Object.<anonymous>.FakeAsyncTestZoneSpec.onInvoke (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:4779:37)
at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3088:43)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:420:56)
at Object.onInvoke (../../../../node_modules/@angular/core/fesm2022/core.mjs:26321:33)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:420:56)
at Zone.Object.<anonymous>.Zone.run (../../../../node_modules/zone.js/bundles/zone-testing-bundle.umd.js:175:47)
at NgZone.run (../../../../node_modules/@angular/core/fesm2022/core.mjs:26175:28)
at _TestBedImpl.createComponent (../../../../node_modules/@angular/core/fesm2022/testing.mjs:26424:41)
at Function.createComponent (../../../../node_modules/@angular/core/fesm2022/testing.mjs:26231:37)
Environment
- Angular: 16.1.7
- CDK/Material: 16.1.6
- Browser(s): N/A
- Operating System (e.g. Windows, macOS, Ubuntu): macOS
I have the same issue with version 15, i tried another way to use MatTestDialogOpener but i came to the same result.
**Error: NG0202:** This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
Here is my MatTestDialogOpener implementation into my before each.
I was wondering that MatTestDialogOpener can wrap my dialog component into a mocked component, so i can test my dialog outside of any parent component context. Thats why I start with calling MatTestDialogOpener.withComponent to create my wrapper component.
Is it the correct way to do this ?
beforeEach(async () => {
DialogWrapperComponent = MatTestDialogOpener.withComponent(ResultsComponent, { data: questionary });
await TestBed.configureTestingModule({
declarations: [
DialogWrapperComponent,
ResultsComponent
],
imports: [
SharedModule //Contians MatDialogModule
]
}).compileComponents();
fixture = TestBed.createComponent(DialogWrapperComponent);
componentWrapper = fixture.componentInstance;
component = componentWrapper.dialogRef.componentInstance;
compiled = fixture.nativeElement;
fixture.detectChanges();
});
Environment Angular: 15.0.4 CDK/Material: 15.0.3 Browser(s): Edge Operating System (e.g. Windows, macOS, Ubuntu): Windows 10
Doing some Googling - and looking at the error message - looks like something that is expected is missing for dependency injection. Not sure where, yet.
Tangentially-related issues: https://github.com/thymikee/jest-preset-angular/issues/467 https://github.com/angular/angular/issues/45155
I also confirmed this behavior - the errors - is consistent in Angular+Material 14.x, 15.x, 16.x, and 17.x when added to an empty, new Angular application created via: ng new
I'm now discovered that if you copy the files: dialog-opener.spec.ts dialog-opener.ts
from the Angular Material directory: src/material/dialog/testing/
and place them in your Angular directory: src/app/
and then modify the imports in dialog-opener.spec.ts to import directly from your local version of dialog-opener.ts like so:
import {MatTestDialogOpenerModule, MatTestDialogOpener} from './dialog-opener';
then the tests in dialog-opener.spec.ts will run successfully locally.
This probably explains why the Angular Material team had no problems running the tests in dialog-opener.spec.ts successfully when run in the Angular Material project.
I got hit by this today - has anyone some progress on testing dialog components?
I can confirm that the tests works if I copy the dialog-opener.ts
into my repository.