Angular 2 - 单元测试绑定到嵌套的自定义表单控件

Posted

技术标签:

【中文标题】Angular 2 - 单元测试绑定到嵌套的自定义表单控件【英文标题】:Angular 2 - Unit test binding to nested custom form control 【发布时间】:2018-01-17 23:39:29 【问题描述】:

我有一个带有选择器app-date-picker 的自定义控件。它实现了ControlValueAccessor。我有一个名为 MyPage 的组件,其中包含此自定义表单控件:

<app-date-picker class="address__from-date" [(ngModel)]="fromDate"></app-date-picker>

我正在尝试为MyPage 编写一个单元测试来测试绑定的两个方向。我已经为其他表单字段做了这个就好了,例如:

it('should bind zip code', fakeAsync(() => 
  const formControl = element.query(By.css('.address__zip-code input')).nativeElement;

  // model -> view
  component.zipCode = 76777;
  fixture.detectChanges();
  tick();

  expect(formControl.value).toEqual('76777');

  // view -> model
  formControl.value = '89556';
  formControl.dispatchEvent(new Event('input'));

  expect(component.zipCode).toEqual(89556);
));

当我尝试为我的自定义表单控件执行此操作时,我的问题出现了。到目前为止,我只能测试一个方向的绑定,即使这样它也需要使用ng-reflect-model,这太糟糕了:

it('should bind from-date', fakeAsync(() => 
  const formControl = element.query(By.css('.address__from-date app-date-picker')).nativeElement;

  // model -> view
  component.fromDate = '01/2017';
  fixture.detectChanges();
  tick();

  expect(formControl.attributes['ng-reflect-model'].value).toEqual('01/2017');

  // view -> model
  // Not sure what to do here either
));

有没有更好的方法来做到这一点?我愿意:

    能够从 MyPage 单元测试中测试绑定的两个方向,这样我就知道我正确地将它连接到表单控件 不必使用ng-reflect-*

其他说明:

MyPage 组件是具有fromDate(和zipCode)字段的标准组件。

自定义表单字段正确实现ControlValueAccessor,有一个date字段,内部使用&lt;input&gt;,它本身是通过ngModel绑定的:

<input [(ngModel)]="date" type="date">

日期选择器组件

@Component(
  selector: 'app-date-picker',
  templateUrl: './date-picker.component.html',
  styleUrls: ['./date-picker.component.scss'],
  providers: [
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DatePickerComponent),
    multi: true
  ]
)
export class DatePickerComponent implements ControlValueAccessor 

  private propagateChange = (_: any) => ;

  private _date: string;
  get date(): string 
    return this._date;
  

  @Input()
  set date(value: string) 
    this._date = value;
    this.propagateChange(value);
  

  writeValue(newValue: any) 
    if (newValue !== undefined) 
      this.date = newValue;
    
  

  registerOnChange(fn: any) 
    this.propagateChange = fn;
  

  registerOnTouched(fn: any) 
    // Not used
  

2017 年 8 月 10 日更新

到目前为止,我已经接近我想要的@ViewChild

@Component(...)
MyPageComponent 
 @ViewChild('fromDate') fromDatePicker: DatePickerComponent;
 ...

MyPage 模板变为(注意模板引用变量#fromDate):

<app-date-picker #fromDate class="address__from-date" [(ngModel)]="fromDate">
</app-date-picker>

那么测试就变成了:

it('should bind from-date', fakeAsync(() => 
  // model -> view
  component.info.fromDate = '01/2017';
  fixture.detectChanges();
  tick();

  expect(component.fromDatePicker.date).toEqual('01/2017');

  // view -> model
  component.fromDatePicker.date = '02/2017';

  expect(component.info.fromDate).toEqual('02/2017');
));

有人知道更好的方法吗?虽然这不是最佳的,但这让我暂时满意。

【问题讨论】:

【参考方案1】:

好的,终于找到了我满意的答案,因为它避免了ng-reflect-*@ViewChild。我可以打电话给.componentInstance,而不是打电话给.nativeElement。那么测试就变得简单了:

it('should bind from-date', fakeAsync(() => 
  const formControl = element.query(By.css('.address__from-date app-date-picker')).componentInstance;

  // model -> view
  component.fromDate = '01/2017';
  fixture.detectChanges();
  tick();

  expect(formControl.date).toEqual('01/2017');

  // view -> model
  formControl.date = '02/2017';

  expect(component.fromDate).toEqual('02/2017');
));

如果有人有更好的解决方案,我仍然愿意,但我暂时将其标记为答案。希望它可以帮助其他遇到此问题的人!

【讨论】:

以上是关于Angular 2 - 单元测试绑定到嵌套的自定义表单控件的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 - 使用旧数据的自定义验证器

嵌套的自定义 FormArray 组件不与具有 FormArrayName 的子表单绑定

Angular2 2方式绑定中同名的自定义输入和输出

angular2 使用主机组件进行单元测试

Angular2如何对自定义验证器指令进行单元测试?

如何根据 Angular 2 中的自定义验证规则显示错误消息?