简体   繁体   中英

Unit testing component with @Input / @Output and reactive form

This is my component:

import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
import { FormBuilder, FormGroup , Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { Values } from '../_models/values';

@Component({
  selector: 'some',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Mycomponent implements OnInit, OnDestroy {

  @Input()
  values: Array<string>;

  @Output()
  selectedValues = new EventEmitter<Values>();

  private myForm: FormGroup;

  @Input()
  errorMsg: string;

  private selectSubscription: Subscription;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      'selectAll': [false],
      'values': [this.values, Validators.required]
    });

    this.selectSubscription = this.myForm.get('selectAll').valueChanges.subscribe(value => {
      this.changeSelection(value);
    });
  }

  submit(): void {
    console.log('called');

    console.log(this.myForm.value.values);

    const theSelectedValues = {
      vals: this.myForm.value.values
    };
    this.selectedValues.emit(theSelectedValues);
  }

  private changeSelection(selectAll: boolean): void {
    if (selectAll) {
      const valuesSelect = this.myForm.controls['values'];
      valuesSelect.disable();
    } else {
      this.myForm.controls['values'].enable();

    }
  }

  ngOnDestroy() {
    this.selectSubscription.unsubscribe();
  }

}

The template:

<form [formGroup]="myForm" (ngSubmit)="submit()">
  <fieldset>
    <mat-checkbox formControlName="all">Select all</mat-checkbox>
  </fieldset>
  <fieldset>
    <select id="chooser" formControlName="values" multiple>
      <option *ngFor="let val of values?.urls" [value]="val">{{val}}</option>
    </select>
  </fieldset>
  <button mat-button [disabled]="myForm.invalid">Go!</button>
</form>
<div *ngIf="errorMsg">{{errorMsg}}</div>

The test

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { Mycomponent } from './my.component';
import { By } from '@angular/platform-browser';
import { FormBuilder } from '@angular/forms';
import { NO_ERRORS_SCHEMA } from '@angular/core';

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


  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [],
      schemas: [NO_ERRORS_SCHEMA],
      declarations: [Mycomponent],
      providers: [
        FormBuilder      ]
    })
      .compileComponents();

  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(Mycomponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

  it('should emit selected values', () => {

    spyOn(component.selectedValues, 'emit');
    component.values = ['abc', 'de'];

    fixture.detectChanges();

    expect(fixture.debugElement.queryAll(By.css('option')).length).toBe(2); // is 0

    const formDE = fixture.debugElement.query(By.css('form'));
    formDE.triggerEventHandler('ngSubmit', {});

    // urls: null
    expect(component.selectedValues.emit).toHaveBeenCalledWith({ vals: ['abc', 'de'] });
  });
});

The test fails because

a)

component.values = ['abc', 'de'];

Does not lead to the form having two option elements

and b)

expect(component.selectedValues.emit).toHaveBeenCalledWith({ vals: ['abc', 'de'] });

Is called, but with { vals: null }

The code works, the app itself works fine, just the test is failing.

How do I set up the form properly, the @Input element?

I have looked at some blog posts but have not been able to adapt them to my code.

This is because you are using Onpush strategy. When onPush is used change detection is propagating down from the parent component not the component itself.

My suggestion is to wrap your test component inside a host component. There is a pending issue regarding this on Angular's gitHub page you can look it up for further reading.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM