Как я должен модульно тестировать компоненты и модули Angular2, которые импортировали другие модули (и компоненты)

#unit-testing #angular #angular-material2

#модульное тестирование #angular #angular-material2

Вопрос:

Я потратил некоторое время на освоение модулей в Angular2, и они мне действительно нравятся, но я немного не уверен в наилучшем подходе к тестированию как моих модулей, так и компонентов внутри. (Я также понимаю, что мой app.component может и, вероятно, должен быть разбит больше, но на данный момент это полезно при изучении структуры тестирования, чтобы быть немного более сложным)

Например, это мое приложение.модуль:

 import { createStore, compose, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import { AUTH_PROVIDERS } from 'angular2-jwt';

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { ComponentsModule } from './components';
import { MaterialModule} from './material';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { ViewsModule } from './ views';

import { rootReducer } from './dataStore';

import { CurrentUserModel } from './models/current-user'


const appStore = createStore(rootReducer, applyMiddleware(ReduxThunk));

const APP_DECLARATIONS = [
  AppComponent
];

const APP_PROVIDERS = [
  { provide: 'AppStore', useValue: appStore },
  CurrentUserModel
];

@NgModule({
  imports:[
    FormsModule,
    BrowserModule,
    RouterModule,// here as well as in our Views Module because of router-outlet
    ViewsModule,
    MaterialModule, // here as well as in our Views amp; componet Module because used in App componet
    ComponentsModule
  ],
  declarations: APP_DECLARATIONS,
  bootstrap:[AppComponent],
  providers: APP_PROVIDERS,
})
export class AppModule {

}
  

и вот как выглядит мой app.component:

 import { Component, ViewChild, AfterViewInit } from '@angular/core';

import { Router } from '@angular/router';

@Component({
  selector: 'app',
  styleUrls:['app.component.scss'],
  template: `
  <md-toolbar>
  <!--  <i class="material-icons demo-toolbar-icon">menu</i> -->
    <span class="toolbar-brand">Franks</span>
    <span *ngIf="searchActive" role="search" class="fill-remaining-space">
        <span class="search-input-container flex flex-1">
          <i class="material-icons search-link">search</i>
          <input class="search-input" placeholder="Search" type="text" id="searchInput" #searchInput (keyup.esc)="exitSearch($event)"/>
        </span>
    </span>
    <i *ngIf="searchActive" class="material-icons right selectable" (click)="exitSearch($event)">close</i>

    <span *ngIf="!searchActive" class="fill-remaining-space">

    </span>
    <span *ngIf="!searchActive" role="navmenu">
      <span class="hlink" routerLink="/" routerLinkActive="active">home</span>
      <span class="hlink" routerLink="/profile" routerLinkActive="active">Profile</span>
      <span class="hlink" routerLink="/login" routerLinkActive="active">Login</span>
      <span class="hlink" routerLink="/signup" routerLinkActive="active">Sign Up</span>
      <i class="material-icons search-link" (click)="activeSearch($event)">search</i>
    </span>
  </md-toolbar>
  <div class="container">
    <router-outlet></router-outlet>
  </div>
  `,
})
export class AppComponent {
  @ViewChild('searchInput') searchInputRef;

  ngAfterViewChecked() {
    if(this.searchActive amp;amp; this.searchInputRef){
      console.log(this.searchInputRef);       
      this.searchInputRef.nativeElement.focus();
    }
  }
  searchActive: boolean;
  constructor(public router: Router) {
    this.searchActive = false;
  }

  activeSearch(event):void {
    this.searchActive = true;
  }

  exitSearch(event) : void {
    this.searchActive = false;
  }
}
  

Итак, я знаю, что потенциально могу воспроизвести все компоненты, например MaterialComponents , с помощью (это всего лишь модуль-оболочка для компонентов материала) в моих тестах, но это кажется немного утомительным. Это мои единственные варианты, и если да, имеет ли смысл создавать макет компонентов при создании компонентов в процессе.

в информационных целях так выглядит мой модуль material, а мои модули views и components похожи:

 import { NgModule } from '@angular/core';

// Material
import { MdCardModule } from '@angular2-material/card';
import { MdButtonModule } from '@angular2-material/button';
import { MdInputModule } from '@angular2-material/input';
import { MdToolbarModule } from '@angular2-material/toolbar';
import { MdListModule } from '@angular2-material/list';
import { MdIconModule, MdIconRegistry } from '@angular2-material/icon';



const MATERIAL_UI_MODULES = [
  MdCardModule,
  MdButtonModule,
  MdInputModule,
  MdToolbarModule,
  MdIconModule,
  MdListModule
]
const MATERIAL_UI_REGISTRIES = [
  MdIconRegistry
]

@NgModule({
  imports:[
    ...MATERIAL_UI_MODULES,
  ],
  providers: MATERIAL_UI_REGISTRIES,
  exports:[
    ...MATERIAL_UI_MODULES,
  ]
})
export class MaterialModule {

}
  

Ответ №1:

Итак, я в конце концов понял, как это сделать, и это мое текущее решение:

 /* tslint:disable:no-unused-variable */
import { TestBed, inject, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { By } from '@angular/platform-browser';

import { Component,ViewChild, AfterViewChecked  } from '@angular/core';
import { Router } from '@angular/router';
import { Location, CommonModule } from '@angular/common';
import { MaterialModule} from './material';

@Component({
  template: '<div></div>'
})
class DummyComponent {
}


import { AppComponent } from './app.component';

describe('component: TestComponent', function () {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        CommonModule,
        RouterTestingModule.withRoutes([
         { path: 'profile', component: DummyComponent },
         { path: 'login', component: DummyComponent },
         { path: 'signup', component: DummyComponent },
         { path: '', component: DummyComponent }
       ]),
       MaterialModule
      ],
      declarations: [ AppComponent, DummyComponent ]
    });
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

  it('should be navigate to correct url for each option in navmenu',
    async(inject([Router, Location], (router: Router, location: Location) => {

    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();

    fixture.debugElement.query(By.css('span.hlink[routerLink="/profile"]')).nativeElement.click();
    fixture.whenStable().then(() => {
      expect(location.path()).toEqual('/profile');
      expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/profile"]')).classes['active']).toBeTruthy();
      expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)

      fixture.debugElement.query(By.css('span.hlink[routerLink="/login"]')).nativeElement.click();
      return fixture.whenStable();
    }).then(() => {
      expect(location.path()).toEqual('/login');
      expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/login"]')).classes['active']).toBeTruthy();
      expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)

      fixture.debugElement.query(By.css('span.hlink[routerLink="/signup"]')).nativeElement.click();
      return fixture.whenStable();
    }).then(() => {
      expect(location.path()).toEqual('/signup');
      expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/signup"]')).classes['active']).toBeTruthy();
      expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)

      fixture.debugElement.query(By.css('span.hlink[routerLink="/"]')).nativeElement.click();
      return fixture.whenStable();
    }).then(() => {
      expect(location.path()).toEqual('/');
      expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/"]')).classes['active']).toBeTruthy();
      expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)
    });

  })));
});