【问题标题】:Angular Testing for Angular-Material on Mat-MenuMat-Menu 上 Angular-Material 的角度测试
【发布时间】:2019-01-17 23:30:21
【问题描述】:

我正在尝试在我的应用程序工具栏中为我的mat-menu 编写测试。当我在测试中调用button.click() 时,控制台中出现Cannot read property 'templateRef' of undefined 错误。

所有作品都可以在浏览器中找到,我相信这与我运行测试的方式有关吗?

app.component.spec.ts

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { RouterTestingModule } from '@angular/router/testing';

import { AppRoutes } from './app.routes';
import {
  MatToolbarModule,
  MatIconModule,
  MatMenuModule,
  MatButtonModule
} from '@angular/material';

import { HomeComponent } from './home/home.component';
import { UserService } from './user/user.service';

class MockUserService {
  signIn() {}
}

describe('AppComponent', () => {
  let app: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent, HomeComponent],
      providers: [{ provide: UserService, useClass: MockUserService }],
      imports: [
        MatIconModule,
        MatToolbarModule,
        MatMenuModule,
        MatButtonModule,
        RouterTestingModule.withRoutes(AppRoutes)
      ]
    }).compileComponents();
  }));
  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    app = fixture.debugElement.componentInstance;
  });
  it('should create the app', async(() => {
    expect(app).toBeTruthy();
  }));
  it('open the menu when clicking on the account button', async () => {
    const dom = fixture.debugElement.nativeElement;
    const button = dom.querySelector('#userMenu');
    button.click();  //Error occurs on this line, before the console.log()
    console.log(button);
    fixture.detectChanges();
  });
});

app.component.html

<mat-menu #menu="matMenu" [overlapTrigger]="false" yPosition="below"> //appears to not be able to find this element?
  <button mat-menu-item>
    <span>Settings</span>
  </button>
  <button id="userSignIn" (click)="user.signIn()" mat-menu-item>
    <span>Log In</span>
  </button>
</mat-menu>
<mat-toolbar color="primary">
  <span>Report Receiver</span>
  <span class="fill-remaining-space"></span>
  <button *ngIf="user.signedIn$ | async" routerLink="/emails/mapping" mat-icon-button>
    <mat-icon>check_box</mat-icon>
  </button>
  <button *ngIf="user.signedIn$ | async" routerLink="/emails/received" mat-icon-button>
    <mat-icon>list</mat-icon>
  </button>
  <button routerLink="/home" mat-icon-button>
    <mat-icon>home</mat-icon>
  </button>
  <button id="userMenu" [matMenuTriggerFor]="menu" mat-icon-button>
    <mat-icon>account_circle</mat-icon>
  </button>
</mat-toolbar>
<router-outlet></router-outlet>

【问题讨论】:

  • 您是否注意到使用材料组件进行测试非常慢?

标签: angular angular-test angular2-material angular-material-6


【解决方案1】:

我的最终测试结果如下:

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { RouterTestingModule } from '@angular/router/testing';

import { AppRoutes } from './app.routes';
import {
  MatToolbarModule,
  MatIconModule,
  MatMenuModule,
  MatButtonModule
} from '@angular/material';

import { HomeComponent } from './home/home.component';
import { UserService } from './user/user.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { BehaviorSubject } from '../../node_modules/rxjs';

class MockUserService {
  signedIn$: BehaviorSubject<boolean> = new BehaviorSubject(false);
  signIn() {}
}

describe('AppComponent', () => {
  let app: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  let dom;
  let button;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent, HomeComponent],
      providers: [{ provide: UserService, useClass: MockUserService }],
      imports: [
        NoopAnimationsModule,
        MatIconModule,
        MatToolbarModule,
        MatMenuModule,
        MatButtonModule,
        RouterTestingModule.withRoutes(AppRoutes)
      ]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    fixture.autoDetectChanges(true);                                           //this was the key fix
    spyOn(MockUserService.prototype, 'signIn').and.callThrough();
    app = fixture.debugElement.componentInstance;
    dom = fixture.nativeElement;
    button = dom.querySelector('#userMenu');
  });

  it('should create the app', async(() => {
    expect(app).toBeTruthy();
  }));

  describe('user menu', () => {
    it('should not have the menu open', async () => {
      const menu = dom.parentNode.querySelector('.mat-menu-panel');
      expect(menu).toBeFalsy();
    });

    it('open the menu when clicking on the account button', async () => {
      button.click();
      const menu = dom.parentNode.querySelector('.mat-menu-panel');
      expect(menu).toBeTruthy();
    });

    it('call user.signIn() once sign in button is pressed', async () => {
      button.click();
      const mockUser = TestBed.get(UserService);
      dom.parentNode.querySelector('#userSignIn').click();
      expect(mockUser['signIn']).toHaveBeenCalled();
    });
  });

  describe('navigation icons', () => {
    it('navigation icons show when user.signedIn$ emits true', async () => {
      const mockUser = TestBed.get(UserService);
      await mockUser.signedIn$.next(true);
      await fixture.detectChanges();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/received"]')
      ).toBeTruthy();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/mapping"]')
      ).toBeTruthy();
    });

    it('navigation icons don\t show until user.signedIn$ emits true', async () => {
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/received"]')
      ).toBeFalsy();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/mapping"]')
      ).toBeFalsy();
    });

    it('navigation icons don\t show until user.signedIn$ emits false', async () => {
      const mockUser = TestBed.get(UserService);
      await mockUser.signedIn$.next(false);
      await fixture.detectChanges();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/received"]')
      ).toBeFalsy();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/mapping"]')
      ).toBeFalsy();
    });

    it('navigation icons re-hide on user.signedIn$ false', async () => {
      const mockUser = TestBed.get(UserService);
      await mockUser.signedIn$.next(true);
      await fixture.detectChanges();
      await mockUser.signedIn$.next(false);
      await fixture.detectChanges();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/received"]')
      ).toBeFalsy();
      expect(
        dom.parentNode.querySelector('button[routerLink="/emails/mapping"]')
      ).toBeFalsy();
    });
  });
});

【讨论】:

    【解决方案2】:

    有更简单的方法:

    你可以在组件中使用@ViewChild

    @ViewChild('menuTrigger') menuTrigger: MatMenuTrigger;
    

    在html模板中有:

     <button mat-icon-button [matMenuTriggerFor]="menu" #menuTrigger='matMenuTrigger'>
      </button>
    

    然后在测试中有“openMenu()”:

     fit('should show menu', () => {
      component.menuTrigger.openMenu()
     }
    

    【讨论】:

    • 当组件上有多个触发器时,这将如何表现?例如每个表行的菜单触发器。
    • 尝试@ViewChildren 监控多个触发器。如果您也添加/删除行,它会自动更新! — angular.io/api/core/ViewChildren
    猜你喜欢
    • 2019-06-16
    • 2019-08-08
    • 1970-01-01
    • 2022-08-19
    • 2021-09-01
    • 2018-12-24
    • 2023-03-16
    • 2021-06-27
    • 2018-11-13
    相关资源
    最近更新 更多