首页 > 解决方案 > 无法读取角度 4 中未定义的属性“值”

问题描述

我正在 Jasmine 中为我的 angular 4 组件编写测试,并收到错误 "Cannot read property 'value' of undefined" 。我在哪里以及如何初始化属性?我最初想改成[(ngModel)]="fedExTax.value"[ngModel]="fedExTax?.value"但我需要双向绑定。有人可以建议我一个解决方案

您可以[(ngModel)]="fedExTax.value"在下面的 html 代码中看到

html代码

<div class="col-lg-3 col-6 mb-3">
        <label class="col-form-label">{{'CAPTIVES.LINES.INCCAPTIVEEXPENSE.' + FedExciseKey|uppercase|translate}}</label>
        <div class="input-group">
          <input type="text" [readonly]="isReadOnly" class="form-control form-control-sm" [(ngModel)]="fedExTax.value"  name="{{FedExciseKey}}" numberFormat="numberPercent:.0-2" (ngModelChange)="change()" [required]="true" tooltip="{{'CAPTIVES.LINES.INCCAPTIVEEXPENSE.' + FedExciseKey + 'TOOLTIP'|uppercase|translate}}"
            placement="bottom">
          <span class="input-group-addon">%</span>
        </div>
      </div>

我的测试代码

import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { UniqueIdPipe } from '@wtw/toolkit/src/pipes/unique-id.pipe';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { IncrementalCaptiveAssumptionsComponent  } from './incremental-captive-expenses.component';
import { createIncrementalExpenses, createFedExciseExpenses } from '../../../../../../test/models';
import { Component, ViewChild } from '@angular/core';

describe('IncrementalCaptiveAssumptionsComponent', () => {
  let harness: HostFormTestComponent;
  let sut: IncrementalCaptiveAssumptionsComponent;
  let fixture: ComponentFixture<HostFormTestComponent>;
  const IncrementalExpenses = createIncrementalExpenses();
  const fedExciseTaxExpenses = createFedExciseExpenses();

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        BrowserAnimationsModule,
        FormsModule,
        TranslateModule.forRoot({
          loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
        })
      ],
      declarations: [ HostFormTestComponent, IncrementalCaptiveAssumptionsComponent, UniqueIdPipe ],
      schemas: [NO_ERRORS_SCHEMA]
    });

    TestBed.overrideModule(BrowserDynamicTestingModule, {
      set: {
          entryComponents: [IncrementalCaptiveAssumptionsComponent],
      }
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HostFormTestComponent);
    harness = fixture.componentInstance;
    fixture.detectChanges();
    sut = harness.componentUnderTest;
    harness.incrementalExpensesInputs = IncrementalExpenses;
    harness.fedExciseExpensesInputs = fedExciseTaxExpenses;
    fixture.detectChanges();
  });

   fit('should set fields correctly when the form is displayed', () => {
    expect(JSON.stringify(sut.managerFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'mgmgtFee')));
    expect(JSON.stringify(sut.actuaryFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'actuaryFee')));
    expect(JSON.stringify(sut.auditorFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'auditFee')));
  });

  @Component({
    template: `
    <form #pageForm="ngForm">
      <incremental-captive-expenses *ngIf="incrementalExpensesInputs"
      [(incrementalExpensesInputs)]="incrementalExpensesInputs"
      [(fedExciseExpensesInputs)]="fedExciseExpensesInputs"
      [currentSelectedCurrency]="currentSelectedCurrency"
      [currentSelected953D]= "currentSelected953D"
      (isValid)="incrementalExpensesValid($event)"></incremental-captive-expenses>
    </form>
    `
  })
  class HostFormTestComponent {
    @ViewChild(IncrementalCaptiveAssumptionsComponent)
    public componentUnderTest: IncrementalCaptiveAssumptionsComponent;
    public incrementalExpensesInputs = createIncrementalExpenses();
    public fedExciseExpensesInputs = createFedExciseExpenses();
    public currentSelectedCurrency = 'USD';
    public currentSelected953D = 0;

    constructor() {
    }

    incrementalExpensesValid(valid) {

    }
  }
});

组件代码

import { Component, SimpleChanges, OnInit, OnChanges, Input, Output, ViewChild, EventEmitter } from '@angular/core';
import { NgForm, NgModelGroup, ControlContainer, FormGroup } from '@angular/forms';
import { Base } from '@wtw/toolkit';
import * as BackendDto from '../../../../../api/dtos';

const ManagerFeesFieldKey = 'mgmgtFee';
const ActuaryFeesFieldKey = 'actuaryFee';
const AuditorFeesFieldKey = 'auditFee';
const FedExTaxFieldKey = 'fedExciseTax';

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'incremental-captive-expenses',
  templateUrl: './incremental-captive-expenses.component.html',
  viewProviders: [{ provide: ControlContainer, useFactory: Base.DynamicFormFactory, deps: [IncrementalCaptiveAssumptionsComponent] }]
})

export class IncrementalCaptiveAssumptionsComponent extends Base.DynamicFormComponent implements OnInit, OnChanges {
  @Input() incrementalExpensesInputs: Array<BackendDto.IncrementalExpense>;
  @Input() fedExciseExpensesInputs: Array<BackendDto.FedExciseExpense>;
  @Input() currentSelectedCurrency: string;
  @Input() currentSelectedCoveragePolicy: number;
  @Input() currentSelected953D: number;
  @Input() show: boolean;
  @Output() isValid = new EventEmitter<boolean>();
  public FieldCategory = BackendDto.DynamicFieldCategory;
  public FedExciseKey = FedExTaxFieldKey;
  public isReadOnly = false;
  private _fedExTax: BackendDto.FedExciseExpense = { value: 0, coveragePolicyTypeId: null, is953D: null };
  @ViewChild('incrementalExpensesGroup') private incrementalExpensesGroup: NgModelGroup;

  constructor(form: NgForm) {
    super(form);
  }

  ngOnInit() {

  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes.show && changes.show.currentValue) {
      setTimeout(() => {
        this.markFormGroupTouched(<FormGroup>this.formDirective.controls[this.incrementalExpensesGroup.name]);
      }, 1);

      this.emitFormValidity();
    }
  }

  change() {
    this.emitFormValidity();
  }

  emitFormValidity() {
    setTimeout(() => {
      this.isValid.emit(this.incrementalExpensesGroup.valid);
    }, 1);
  }

  get managerFees(): BackendDto.IncrementalExpense {
    return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === ManagerFeesFieldKey);
  }

  get actuaryFees(): BackendDto.IncrementalExpense {
    return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === ActuaryFeesFieldKey);
  }

  get auditorFees(): BackendDto.IncrementalExpense {
    return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === AuditorFeesFieldKey);
  }

  get fedExTax(): BackendDto.FedExciseExpense {
    if (this.currentSelectedCoveragePolicy !== 0) {
      this.isReadOnly = false;
      return this.fedExciseExpensesInputs.find(x => x.coveragePolicyTypeId === this.currentSelectedCoveragePolicy && x.is953D === this.currentSelected953D);
    } else {
      this.isReadOnly = true;
      return this._fedExTax;
    }
  }

  private markFormGroupTouched(formGroup: FormGroup) {
    if (formGroup && formGroup.controls) {
      Object.keys(formGroup.controls).forEach(key => {
        const control = formGroup.controls[key];
        if (control) {
          control.markAsTouched();
        }
      });
    }
  }

}

模型.ts

export const createFedExciseExpenses = (): Array<BackendDto.FedExciseExpense> => {
    return [
        {is953D : 1, coveragePolicyTypeId : 1, value : 0},
        {is953D : 0, coveragePolicyTypeId : 1, value : 1},
        {is953D : 0, coveragePolicyTypeId : 2, value : 4},
        {is953D : 1, coveragePolicyTypeId : 2, value : 0}
    ];
};

标签: angularjasmine

解决方案


听起来您没有正确设置模拟。在生产中,您说fedEx始终定义属性,但在测试期间并非如此。

这意味着 getter 返回未定义。很可能是这一行:

return this.fedExciseExpensesInputs
  .find(x => 
    x.coveragePolicyTypeId === this.currentSelectedCoveragePolicy && 
    x.is953D === this.currentSelected953D);

对您的代码一无所知,但对单元测试了解很多,我的第一个猜测是检查它this.fedExciseExpensesInputs是否为非空并包含一个可由 getter 返回的值。您没有显示代码中的初始化位置。


推荐阅读