27
votes

I'm trying to teach myself how to code with Angular and I have a problem. I am creating a app for myself and I have just implemented the Angular Material Dialog. I put this in a wrapper service and all seems well. So in a component I call the Wrapper Service to raise a modal like so...

public assignInstrument(instrument: any): void {
    this.modalDialogWrapperService.openAssignmentWindow({
        product: 'instrument',
        type: instrument.type,
        serial: instrument.serial,
        id: instrument.id
  });
}

the service method looks like so, notice I pass the name of a component I wish to raise in the modal window

openAssignmentWindow(instrument) {
    const dialogRef = this.dialog.open(ChangeAssignmentComponent, {
        data: instrument,
        width: '693px',
        height: '498px'
        });
    dialogRef.afterClosed().subscribe(() => {});
    });
}

Everything works great! But as a good developer I should write unit tests... so to test my component I have the following test (I have included how I mock the Service and some other code to give an impression of the test file)

let modalDialogWrapperServiceSpy: jasmine.SpyObj<ModalDialogWrapperService>;
    const mockModalDialogWrapperService = jasmine.createSpyObj('ModalDialogWrapperService', ['openAssignmentWindow']);
    mockModalDialogWrapperService.openAssignmentWindow.and.returnValue({});

TestBed.configureTestingModule({
    imports: [...],
    declarations: [...],
    providers: [{
      provide: ModalDialogWrapperService,
      useValue: mockModalDialogWrapperService
    }]
}).compileComponents();

beforeEach(() => {
    fixture = TestBed.createComponent(InstrumentsPageComponent);
    modalDialogWrapperServiceSpy = TestBed.get(ModalDialogWrapperService);
    component = fixture.componentInstance;
    fixture.detectChanges();
 })

describe('assignInstrument', () => {
  it('should call the Modal Dialog Service', () => {
    component.assignInstrument({});
    expect(modalDialogWrapperServiceSpy.openAssignmentWindow).toHaveBeenCalledTimes(1);
  });
});

This one test fails! With the error "Error: No component factory found for ChangeAssignmentComponent. Did you add it to @NgModule.entryComponents" - this seems odd as in my app.module file I declare "ChangeAssignmentComponent" in entryComponents and declarations arrays? I am confused - does anyone know what I could be doing wrong?

3
Have you imported the module in your spec in which your component is declared and added as entryComponents? - Amit Chigadani
There is only one module in my app, so I don't declare it in the imports array of my testbed for example: imports: [MatTableModule, MatPaginatorModule, MatDialogModule, NoopAnimationsModule], - NewToAngular

3 Answers

48
votes

Testing is doubting.

More seriously, let me answer you.

In Angular, your components are handled by a module. When you use Material dialogs and snackers, you actually use a feature of the CDK, which is called Portal. This allow you to create your components dynamically.

But when you do so, you have to add them to the entryComponents of your module. You did it in your module, so you should also do it in your tests.

The syntax is

TestBed
  .configureTestingModule(...)
  .overrideModule(BrowserDynamicTestingModule, { set: { entryComponents: [YourComponent] } });
19
votes

there are two places at which this is supposed to be done....entry components and also at declarations(while configuring your testing module)....

  TestBed
  .configureTestingModule({
              declarations: [YourComponent],
   })
  .overrideModule(BrowserDynamicTestingModule, { set: { entryComponents: [YourComponent] } });
5
votes

If someone struggling to find BrowserDynamicTestingModule just use BrowserModule instead.

   TestBed
  .configureTestingModule(...)
  .overrideModule(BrowserModule, { set: { entryComponents: [YourComponent] } });