首页 > 解决方案 > Mat Table - renderRows 单元测试错误

问题描述

我在组件中使用 mat 表,并在更新表后调用 renderRows,效果很好。但是,在我的单元测试中,我收到以下错误。

afterAll Failed: Cannot read property 'renderRows' of undefined error properties: Object({ longStack: 'TypeError: Cannot read property 'renderRows' of undefined at 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,单元测试正在运行,没有任何问题。对这里的问题有任何想法吗?

编辑:

模拟税收配置服务

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();
    }

   ...
}

标签: angularunit-testingkarma-jasminemat-table

解决方案


I think you unit test actually reveals a problem which you didn't encounter because your real tax configuration service just took long enough, until the table was actually initialised.

A view child is not available in onInit. It is set a little bit later in the life cycle. You would need to use ngAfterViewInit for that.

Have a look here

The error is shown in your test, because with the first fixture.detectChanges() inside your beforeEach you are triggering ngOnInit. Your tax service mock emits immediately your mock values and since the ViewChild is not initialised until ngAfterViewInit your table is still undefined


推荐阅读