Jasmin:如何在Angular 8中模拟@ViewChild组件

时间:2019-10-24 10:14:35

标签: angular unit-testing jasmine mocking viewchild

我正在尝试使用也存储为@ViewChild

的子组件来测试组件

project-estimation.component.ts

import { Component, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { ProjectCreateComponent } from './project-create/project-create.component';
import { ProjectCaracComponent } from './project-carac/project-carac.component';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-project-estimation',
  templateUrl: './project-estimation.component.html',
  styleUrls: ['./project-estimation.component.css']
})
export class ProjectEstimationComponent implements OnInit, AfterViewInit, OnDestroy {

  @ViewChild(ProjectCreateComponent, {static: false}) createComp: ProjectCreateComponent;
  @ViewChild(ProjectCaracComponent, {static: false}) caracComp: ProjectCaracComponent;

  public isCreateStepCompleted = false;
  public isCaracStepCompleted = false;
  private subTokens: Subscription[] = [];

  constructor() { }

  public ngOnInit() {
  }

  public ngAfterViewInit(): void {

    this.subTokens.push(this.createComp.projectTypeForm.statusChanges
      .subscribe((status) => {
      this.isCreateStepCompleted = this.isFormStatusValid(status);
    }));

    this.subTokens.push(this.caracComp.projectCaracForm.statusChanges
      .subscribe((status) => {
      this.isCaracStepCompleted = this.isFormStatusValid(status);
    }));
  }

  public ngOnDestroy(): void {
    for (const token of this.subTokens) {
      token.unsubscribe();
    }
  }

  public isFormStatusValid(status: string) {
    return status === 'VALID';
  }
}

project-estimation.component.html

<mat-card>
  <mat-card-title>
    <p>Estimation de projet</p>
  </mat-card-title>

  <mat-card-content>
    <mat-horizontal-stepper linear #stepper>
        <mat-step id="createStep" [completed]="isCreateStepCompleted">
          <ng-template matStepLabel>Créez votre projet</ng-template>
          <app-project-create></app-project-create>
        </mat-step>
        <mat-step id="caracStep" [completed]="isCharacStepCompleted">
          <ng-template matStepLabel>Définissez les characteristiques</ng-template>
          <app-project-charac></app-project-charac>
        </mat-step>
        <mat-step id="quotationStep" [completed]="isQuotationStepCompleted">
          <ng-template matStepLabel>Récupérez votre estimation</ng-template>
          <app-project-quotation></app-project-quotation>
        </mat-step>
    </mat-horizontal-stepper>
  </mat-card-content>
</mat-card>

现在,我想设置一个非回归测试,以确保如果子组件之一发出statusChanges,则状态将正确更新。

这是我尝试设置的测试

project-estimation.components.spect.ts

class FormGroupMock {

  public status: string;
  public statusChanges: BehaviorSubject<string>;

  public constructor() {

    this.status = 'VALID';
    this.statusChanges = new BehaviorSubject(this.status);
  }

  public setStatus(status: string): void {
    this.status = status;
    this.statusChanges.next(this.status);
  }
}

@Component({
  selector: 'app-project-create',
  template: ''
})
class CreateCompMockComponent {
  public projectTypeForm = new FormGroupMock();
}

@Component({
  selector: 'app-project-carac',
  template: ''
})
class CaracCompMockDComponent {
  public projectCaracForm = new FormGroupMock();
}

describe('ProjectEstimationComponent', () => {
  let component: ProjectEstimationComponent;
  let fixture: ComponentFixture<ProjectEstimationComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        MaterialModule,
        RouterTestingModule.withRoutes(testProjectEstimationRoutes),
        ReactiveFormsModule,
        BrowserAnimationsModule
      ],
      declarations: [
        DummyComponent,
        CreateCompMockComponent,
        CaracCompMockDComponent,
        ProjectEstimationComponent,
      ],
      providers: [
      ]
    })
    .compileComponents().then(() => {
      fixture = TestBed.createComponent(ProjectEstimationComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
    });
  }));

  it('should be created', () => {
    expect(component).toBeTruthy();
  });
}

但是,当我运行此测试时,我得到了:

Failed: Uncaught (in promise): TypeError: Cannot read property 'projectTypeForm' of undefined
TypeError: Cannot read property 'projectTypeForm' of undefined
    at ProjectEstimationComponent.ngAfterViewInit (http://localhost:9876/_karma_webpack_/src/app/project-estimation/project-estimation.component.ts:32:41)
...

因此,如您所见,模拟组件未用作主要组件的ViewChild。 如何解决此问题,以便可以使用那些模拟组件来编写更复杂的测试,如下所示:

it('should listen for sub component changes', () => {
    const createComp: CreateCompMockComponent = TestBed.get(ProjectCreateComponent);
    const caracComp: CaracCompMockDComponent = TestBed.get(ProjectCaracComponent);

    expect(component.isCreateStepCompleted).toBe(true);
    expect(component.isCaracStepCompleted).toBe(true);

    createComp.projectTypeForm.setStatus('INVALID');
    caracComp.projectCaracForm.setStatus('INVALID');

    fixture.detectChanges();

    expect(component.isCreateStepCompleted).toBe(false);
    expect(component.isCaracStepCompleted).toBe(false);
  });

1 个答案:

答案 0 :(得分:0)

This link将向您展示如何执行此操作的示例,特别是在Adding a provider to the stub component部分中。 基本上,您的模拟程序需要提供自身,然后允许您的测试按您期望的那样创建viewChild。

class FormGroupMock {

  public status: string;
  public statusChanges: BehaviorSubject<string>;

  public constructor() {

    this.status = 'VALID';
    this.statusChanges = new BehaviorSubject(this.status);
  }

  public setStatus(status: string): void {
    this.status = status;
    this.statusChanges.next(this.status);
  }
}

@Component({
  selector: 'app-project-create',
  template: '<div></div>',
  providers: [{ provide: ProjectCreateComponent, useClass: CreateCompMockComponent }],
})
class CreateCompMockComponent {
  public projectTypeForm = new FormGroupMock();

  setStatus(status: string): void {

  }
}

@Component({
  selector: 'app-project-charac',
  template: '<div></div>',
  providers: [{ provide: ProjectCaracComponent, useClass: CaracCompMockDComponent }],
})
class CaracCompMockDComponent {
  public projectCaracForm = new FormGroupMock();

  setStatus(status: string): void {

  }
}

fdescribe('ProjectEstimationComponent', () => {
  let component: ProjectEstimationComponent;
  let fixture: ComponentFixture<ProjectEstimationComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        // MaterialModule,
        // RouterTestingModule.withRoutes(testProjectEstimationRoutes),
        ReactiveFormsModule,
        BrowserAnimationsModule,
      ],
      declarations: [
        // DummyComponent,
        CreateCompMockComponent,
        CaracCompMockDComponent,
        ProjectEstimationComponent,
      ],
      providers: [
      ],

    })
      .compileComponents().then(() => {
        fixture = TestBed.createComponent(ProjectEstimationComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
  }));

  it('should be created', () => {
    expect(component).toBeTruthy();
  });

  it('should listen for sub component changes', () => {
    // these don't work at the moment, but that can be fixed more easily  
    expect(component.isCreateStepCompleted).toBe(true);
    expect(component.isCaracStepCompleted).toBe(true);

    component.createComp.setStatus('INVALID');
    component.caracComp.setStatus('INVALID');

    fixture.detectChanges();

    expect(component.isCreateStepCompleted).toBe(false);
    expect(component.isCaracStepCompleted).toBe(false);
  });
});

在这里,我将您的html缩小为仅受影响的组件。您可以根据需要开始添加。

<app-project-create></app-project-create>

<app-project-charac></app-project-charac>