Mat Table - renderRows 单元测试错误
Mat Table - renderRows Unit Testing error
我在组件中使用垫子 table 并在更新 table 后调用 renderRows,效果很好。
但是,在我的单元测试中,出现以下错误。
afterAll 抛出错误
失败:无法读取未定义的 属性 'renderRows'
错误属性:对象({ longStack:'TypeError:无法读取未定义的属性 'renderRows'
在 SafeSubscriber._next (http://localhost:9876/karma_webpack/src/app/product-management/tax-configuration/tax-configuration.component.ts:80:23)
spec.ts 文件 ->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaxConfigurationComponent } from './tax-configuration.component';
import { MatTableModule } from '@angular/material/table';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NotificationService } from 'src/app/services/custom/notification.service';
import { TaxConfigurationService } from 'src/app/services/products/tax-configuration.service';
import { MockTaxConfigurationService } from 'src/app/services/products/tax-configuration.service.mock.spec';
import { throwError } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
describe('TaxConfigurationComponent', () => {
let component: TaxConfigurationComponent;
let fixture: ComponentFixture<TaxConfigurationComponent>;
let _notificationService: NotificationService;
let _taxService: TaxConfigurationService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TaxConfigurationComponent],
imports: [
BrowserAnimationsModule,
MatTableModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
ReactiveFormsModule,
HttpClientTestingModule,
MatButtonModule,
],
providers: [{ provide: TaxConfigurationService, useClass: MockTaxConfigurationService }],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaxConfigurationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
_taxService = TestBed.inject(TaxConfigurationService);
_notificationService = TestBed.inject(NotificationService);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should populate tax table on init', () => {
expect(component.dataSource.length).toBeGreaterThan(0);
});
it('should show an error notification when "getTaxConfig()" errors out', () => {
spyOn(_notificationService, 'startNotification').and.callThrough();
spyOn(_taxService, 'getTaxConfig').and.returnValue(throwError('Error'));
component.ngOnInit();
expect(_notificationService.startNotification).toHaveBeenCalledWith(
'An error occurred while fetching data.',
'nc-notification--error',
'priority_high'
);
});
});
component.ts 文件 ->
ngOnInit(): void {
this.state = true;
this.taxForm = new FormGroup({});
this.populateTaxConfigTable();
}
populateTaxConfigTable(): void {
this._taxService.getTaxConfig().subscribe((results) => {
results.forEach((result) => {
const rowEntry = {
name: result.resourceName,
category: result.resourceCategory,
id: result.resourceId,
tsc: new FormControl(result.taxTsc, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
ot: new FormControl(result.taxOt, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
vat: new FormControl(result.taxVat, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
};
const tscControlName = rowEntry.id + 'tsc';
const otControlName = rowEntry.id + 'ot';
const vatControlName = rowEntry.id + 'vat';
this.taxForm.addControl(tscControlName, rowEntry.tsc);
this.taxForm.addControl(otControlName, rowEntry.ot);
this.taxForm.addControl(vatControlName, rowEntry.vat);
this.dataSource.push(rowEntry);
});
this.table.renderRows();
this.state = false;
}, (error) => {
this._notificationService.startNotification('An error occurred while fetching data.',
'nc-notification--error', 'priority_high');
});
}
当我评论 this.table.renderRows
时,单元测试 运行 没有任何问题。对这里的问题有什么想法吗?
编辑:
MockTaxCongfigurationService
export class MockTaxConfigurationService {
getTaxConfig(): Observable<ResourceTaxes[]> {
return of([mockResourceTaxes, mockResourceTaxes]);
}
updateTaxConfig(data: TaxPostData[]): Observable<TaxResponseData[]> {
return of([mockTaxResponseData]);
}
}
使用 viewChild ->
export class TaxConfigurationComponent implements OnInit {
@ViewChild(MatTable) table: MatTable<any>;
displayedColumns: string[] = ['name', 'tsc', 'ot', 'vat'];
taxForm: FormGroup;
dataSource: TaxTableData[] = [];
state = false; // Loading state
shouldDisableSave = false;
constructor(
private _notificationService: NotificationService,
private _taxService: TaxConfigurationService) {}
ngOnInit(): void {
this.state = true;
this.taxForm = new FormGroup({});
this.populateTaxConfigTable();
}
...
}
我认为你的单元测试实际上揭示了一个你没有遇到的问题,因为你真正的税收配置服务只花了足够长的时间,直到 table 被实际初始化。
onInit 中的子视图不可用。它在生命周期中设置得稍晚一些。为此,您需要使用 ngAfterViewInit
。
看看here
错误显示在您的测试中,因为您的 beforeEach
中的第一个 fixture.detectChanges()
触发了 ngOnInit
。您的税务服务模拟会立即发出您的模拟值,并且由于 ViewChild
直到 ngAfterViewInit
才初始化,您的 table 仍然是 undefined
我在组件中使用垫子 table 并在更新 table 后调用 renderRows,效果很好。 但是,在我的单元测试中,出现以下错误。
afterAll 抛出错误 失败:无法读取未定义的 属性 'renderRows' 错误属性:对象({ longStack:'TypeError:无法读取未定义的属性 'renderRows' 在 SafeSubscriber._next (http://localhost:9876/karma_webpack/src/app/product-management/tax-configuration/tax-configuration.component.ts:80:23)
spec.ts 文件 ->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaxConfigurationComponent } from './tax-configuration.component';
import { MatTableModule } from '@angular/material/table';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NotificationService } from 'src/app/services/custom/notification.service';
import { TaxConfigurationService } from 'src/app/services/products/tax-configuration.service';
import { MockTaxConfigurationService } from 'src/app/services/products/tax-configuration.service.mock.spec';
import { throwError } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
describe('TaxConfigurationComponent', () => {
let component: TaxConfigurationComponent;
let fixture: ComponentFixture<TaxConfigurationComponent>;
let _notificationService: NotificationService;
let _taxService: TaxConfigurationService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TaxConfigurationComponent],
imports: [
BrowserAnimationsModule,
MatTableModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
ReactiveFormsModule,
HttpClientTestingModule,
MatButtonModule,
],
providers: [{ provide: TaxConfigurationService, useClass: MockTaxConfigurationService }],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaxConfigurationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
_taxService = TestBed.inject(TaxConfigurationService);
_notificationService = TestBed.inject(NotificationService);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should populate tax table on init', () => {
expect(component.dataSource.length).toBeGreaterThan(0);
});
it('should show an error notification when "getTaxConfig()" errors out', () => {
spyOn(_notificationService, 'startNotification').and.callThrough();
spyOn(_taxService, 'getTaxConfig').and.returnValue(throwError('Error'));
component.ngOnInit();
expect(_notificationService.startNotification).toHaveBeenCalledWith(
'An error occurred while fetching data.',
'nc-notification--error',
'priority_high'
);
});
});
component.ts 文件 ->
ngOnInit(): void {
this.state = true;
this.taxForm = new FormGroup({});
this.populateTaxConfigTable();
}
populateTaxConfigTable(): void {
this._taxService.getTaxConfig().subscribe((results) => {
results.forEach((result) => {
const rowEntry = {
name: result.resourceName,
category: result.resourceCategory,
id: result.resourceId,
tsc: new FormControl(result.taxTsc, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
ot: new FormControl(result.taxOt, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
vat: new FormControl(result.taxVat, [
Validators.required,
Validators.pattern(regexPattern),
Validators.max(100),
Validators.min(0),
]),
};
const tscControlName = rowEntry.id + 'tsc';
const otControlName = rowEntry.id + 'ot';
const vatControlName = rowEntry.id + 'vat';
this.taxForm.addControl(tscControlName, rowEntry.tsc);
this.taxForm.addControl(otControlName, rowEntry.ot);
this.taxForm.addControl(vatControlName, rowEntry.vat);
this.dataSource.push(rowEntry);
});
this.table.renderRows();
this.state = false;
}, (error) => {
this._notificationService.startNotification('An error occurred while fetching data.',
'nc-notification--error', 'priority_high');
});
}
当我评论 this.table.renderRows
时,单元测试 运行 没有任何问题。对这里的问题有什么想法吗?
编辑:
MockTaxCongfigurationService
export class MockTaxConfigurationService {
getTaxConfig(): Observable<ResourceTaxes[]> {
return of([mockResourceTaxes, mockResourceTaxes]);
}
updateTaxConfig(data: TaxPostData[]): Observable<TaxResponseData[]> {
return of([mockTaxResponseData]);
}
}
使用 viewChild ->
export class TaxConfigurationComponent implements OnInit {
@ViewChild(MatTable) table: MatTable<any>;
displayedColumns: string[] = ['name', 'tsc', 'ot', 'vat'];
taxForm: FormGroup;
dataSource: TaxTableData[] = [];
state = false; // Loading state
shouldDisableSave = false;
constructor(
private _notificationService: NotificationService,
private _taxService: TaxConfigurationService) {}
ngOnInit(): void {
this.state = true;
this.taxForm = new FormGroup({});
this.populateTaxConfigTable();
}
...
}
我认为你的单元测试实际上揭示了一个你没有遇到的问题,因为你真正的税收配置服务只花了足够长的时间,直到 table 被实际初始化。
onInit 中的子视图不可用。它在生命周期中设置得稍晚一些。为此,您需要使用 ngAfterViewInit
。
看看here
错误显示在您的测试中,因为您的 beforeEach
中的第一个 fixture.detectChanges()
触发了 ngOnInit
。您的税务服务模拟会立即发出您的模拟值,并且由于 ViewChild
直到 ngAfterViewInit
才初始化,您的 table 仍然是 undefined