angular - 无法读取角度 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}
];
};
解决方案
听起来您没有正确设置模拟。在生产中,您说fedEx
始终定义属性,但在测试期间并非如此。
这意味着 getter 返回未定义。很可能是这一行:
return this.fedExciseExpensesInputs
.find(x =>
x.coveragePolicyTypeId === this.currentSelectedCoveragePolicy &&
x.is953D === this.currentSelected953D);
对您的代码一无所知,但对单元测试了解很多,我的第一个猜测是检查它this.fedExciseExpensesInputs
是否为非空并包含一个可由 getter 返回的值。您没有显示代码中的初始化位置。
推荐阅读
- django - 在 Django 数据库中保存表示图像的 Base64 字符串
- typescript - 如何在 typescript 中声明一个类,就像使用“class”关键字一样?
- c++ - 为什么 round() 让我的表达给出错误的答案?
- reactjs - 使用 react-table-6 从 json 递归创建嵌套表
- keycloak - 使用 keycloak OIDC [SSL: CERTIFICATE_VERIFY_FAILED] 打开 edx
- css - 用 sass 更改 Bootstrap 5 颜色,但指定互补色
- php - 是否可以在使用 PHP 更新 cookie 后获得新值,而无需刷新屏幕?
- iframe - 跟踪 iframe 来源
- c# - LiteDb.Engine.PageBuffer 内存泄漏
- powershell - 使用 Powershell 解析 html 标签链接