【问题标题】:Jasmin : How to mock @ViewChild component in Angular 8Jasmin:如何在 Angular 8 中模拟 @ViewChild 组件
【发布时间】:2020-02-20 15:51:55
【问题描述】:

我正在尝试使用子组件测试一个组件,这些子组件也存储为@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)
...

如您所见,mock 组件并没有用作主组件的 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);
  });

【问题讨论】:

    标签: angular unit-testing jasmine mocking viewchild


    【解决方案1】:

    Angular Unit Testing @ViewChild 将向您展示如何执行此操作的示例,特别是在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>
    

    【讨论】:

    • 我已经尝试过这个解决方案(我刚刚再次尝试)但我仍然得到:Failed: Template parse errors: 'app-project-create' is not a known element:
    • 奇数。这表明您没有将它们放在 TestBed 的声明中。选择器应该与它们正在模拟的组件相匹配,因此如果不是“app-project-create”,那么您需要更新模拟以具有与真实组件相同的选择器。
    • 我将更新我的问题以包含 html,但正如您将看到的,选择器是相同的
    • 在本地尝试时,我得到了一些不同的东西,但我认为我使用的是更新版本的 Angular Material。当我将 html 分解为您的这两个组件时,它会得到不同的结果。
    • 你有没有像我预期的那样工作或者你有不同的错误?
    猜你喜欢
    • 2020-06-02
    • 2020-04-03
    • 2019-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    • 2018-01-24
    • 1970-01-01
    相关资源
    最近更新 更多