Components: Unit testing dialogs not intuitive

Created on 25 Apr 2017  路  9Comments  路  Source: angular/components

While writing unit tests for a function that handles a angular material 2 dialog using the example code from material 2 i run into problems.
I'm a Jasmine newbie but I didn't had problems to write unit test before.
I have to test the result of the afterClose function but i can't get the handle on dialogRef.

For sure there is a workaround but since this was the first function that was really hard to test and someone liked my post on stackoverflow about it maybe refactoring could make it easier ?
http://stackoverflow.com/questions/42852816/unit-testing-angular-material-2-dialogs

            let dialogRef = this.dialog.open(ExtractPageDialog, {
                width: this.EXPORT_DIALOG_WIDTH,
                data: {
                    document: this.document
                }
            });
            dialogRef.afterClosed().subscribe((result: any) => {
                if (result) {
                    let fileId = this.document.fileId;
                    this.docProvider.extractPage(this.document.fileId, result.fromPage, result.toPage).subscribe(() => {
                       () => { //totest },
                       (error) => { //totest }
                    });
                } else {
                    //totest
                }
            });

DOCS:
https://material.angular.io/components/component/dialog

Proposal, change the example to be easier to test:

dialogRef.afterClosed().subscribe(this.functionName);

functionName(result: any) {
                if (result) {
                    let fileId = this.document.fileId;
                    this.docProvider.extractPage(this.document.fileId, result.fromPage, result.toPage).subscribe(() => {
                       () => { //totest },
                       (error) => { //totest }
                    });
                } else {
                    //totest
                }
            }
}

Most helpful comment

Even though this issue has been closed I must say I have never seen such a complex and non-understandable unit tests definition with so many generic variables. overlayContainerElement, viewContainerFixture, ComponentWithChildViewContainer etc..

You have to know each class and its definition perfectly to understand the code. Who would need comments anyways, right?

All 9 comments

Even though this issue has been closed I must say I have never seen such a complex and non-understandable unit tests definition with so many generic variables. overlayContainerElement, viewContainerFixture, ComponentWithChildViewContainer etc..

You have to know each class and its definition perfectly to understand the code. Who would need comments anyways, right?

Has how unit tests are handled been updated over the past year?

I have to say... I think I got this working, but with Angular6... OMG the boilerplate. I'll post if anyone is interested.

I have to say... I think I got this working, but with Angular6... OMG the boilerplate. I'll post if anyone is interested.

Please share you solution, I am still struggling to test.

Angular 6.1.1
Material 6.4.3

40+ lines of boiler plate. I learn more about Angular when writing tests than anything else!

Very simple "save" and "cancel" test for EditDialogComponent:

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: 'view-container-directive'
})
class ViewContainerDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

@Component({
  selector: 'app-view-container-component',
  template: `<view-container-directive></view-container-directive>`,
})
class ViewContainerComponent {
  @ViewChild(ViewContainerDirective) childWithViewContainer: ViewContainerDirective;

  get childViewContainer() {
    return this.childWithViewContainer.viewContainerRef;
  }
}

describe('EditObjectiveDialog', () => {
  let dialog: MatDialog;
  let overlayContainerElement: HTMLElement;

  let testViewContainerRef: ViewContainerRef;
  let viewContainerFixture: ComponentFixture<ViewContainerComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ViewContainerComponent,
        ViewContainerDirective,
        EditDialogComponent,
      ],
      imports: [
        MaterialModule // module of material modules I use
      ],
      providers: [
        { provide: OverlayContainer, useFactory: () => {
          overlayContainerElement = document.createElement('div');
          return { getContainerElement: () => overlayContainerElement };
        }}
      ],
    });

    TestBed.overrideModule(BrowserDynamicTestingModule, {
     // not sure why I needed this, but error message told me to include it
     set: {
        entryComponents: [ EditDialogComponent ]
      }
    });

    TestBed.compileComponents();
  }));

  beforeEach(() => {
    viewContainerFixture = TestBed.createComponent(ViewContainerComponent);
    viewContainerFixture.detectChanges();
    testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer;
  });

  beforeEach(inject([MatDialog], (d: MatDialog) => {
    dialog = d;
  }));

  describe('Save and Cancel', () => {
    let testStructureObjective: StructureObjective;
    let dialogRef: MatDialogRef<EditDialogComponent, any>;
    let afterCloseCallback: jasmine.Spy;

    beforeEach(() => {
      dialogRef = dialog.open(EditDialogComponent, {
        viewContainerRef: testViewContainerRef,
        data: {
            // DialogData goes here
      }});

      afterCloseCallback = jasmine.createSpy('afterClose callback');
      dialogRef.afterClosed().subscribe(afterCloseCallback);
    });

    it('should return input on save if no edits', async(() => {
      // no edits
      // click save
      const saveButton: DebugElement = viewContainerFixture.debugElement.query(By.css('#saveButton'));
      saveButton.triggerEventHandler('click', null);

      viewContainerFixture.detectChanges();

      viewContainerFixture.whenStable().then(() => {
        expect(afterCloseCallback).toHaveBeenCalledWith({...});
        expect(dialogRef.componentInstance).toBeFalsy(); // is closed
      });
    }));

    it('should return undefined if cancelled', async(() => {
      // no edits
      // click cancel
      const cancelButton: DebugElement = viewContainerFixture.debugElement.query(By.css('#cancelButton'));
      cancelButton.triggerEventHandler('click', null);

      viewContainerFixture.detectChanges();

      viewContainerFixture.whenStable().then(() => {
        expect(afterCloseCallback).toHaveBeenCalledWith(undefined);
        expect(dialogRef.componentInstance).toBeFalsy(); // is closed
      });
    }));

  });

template: <view-container-directive></view-container-directive>,

@fizxmike Could you expain what is app-view-container-component in your code?

@fizxmike, did you get this to work with Angular v9?

Had to add { static: true } to get the directive to work in Angular 8

export class DialogContainerComponent {
  @ViewChild(DialogContainerDirective, { static: true })
  childWithViewContainer: DialogContainerDirective;

  get childViewContainer() {
    return this.childWithViewContainer.viewContainerRef;
  }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kara picture kara  路  3Comments

michaelb-01 picture michaelb-01  路  3Comments

shlomiassaf picture shlomiassaf  路  3Comments

LoganDupont picture LoganDupont  路  3Comments

jelbourn picture jelbourn  路  3Comments