Angular中含ngOnInit的Mat-Dialog组件单元测试覆盖率问题
Angular Mat-Dialog组件单元测试:ngOnInit及方法未被代码覆盖
问题描述
我正在尝试对带有ngOnInit方法的Angular Mat-Dialog组件进行单元测试,目前仅构造函数代码被覆盖,其余方法无法调用。我已模拟Mat-Dialog、FormsModule及相关服务,以下是待测试的组件TS文件和测试Spec文件:
组件TS文件
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialog,MatDialogRef } from '@angular/material/dialog'; import { PieceManagementService } from 'src/app/shared/services/piece-management/piece-management.service'; import { CommonService } from 'src/app/shared/services/common.service'; import { commonConstants } from 'src/app/shared/utils/constant'; @Component({ selector: "app-add-pieces", templateUrl: "./add-pieces.component.html", styleUrls: ["./add-pieces.component.scss"] }) export class AddPiecesComponent implements OnInit { selectedPieces:any = []; pieces:any = []; errorPiece : string; empForm: FormGroup; pieceName:any; nom:any; errorMessage:string; public pieceForm: FormGroup; public pieceValidationMessages = commonConstants.pieceValidationMessages; constructor(private _fb: FormBuilder, private pieceManagement:PieceManagementService, public dialogRef: MatDialogRef<AddPiecesComponent>, private commonService:CommonService) { } ngOnInit(): void { this.createPiecesForm(); this.fetchAllZones(); } public fetchAllZones(){ this.pieceManagement.fetchAllZones().subscribe((response: any) => { response.forEach((item)=>{ return item.active = false; }) this.pieces = response; console.log(this.pieces); }); } createPiecesForm(){ this.pieceForm = this._fb.group({ nom: ['',Validators.required] }); } activateClass(subModule){ subModule.active = !subModule.active; subModule.activeli = !subModule.activeli; if (this.selectedPieces.length === 0 && subModule.active === true){ this.selectedPieces.push(subModule); } else { let index = this.selectedPieces.findIndex((piece) => piece.zoneId === subModule.zoneId); if (subModule.active === true && index === -1) { this.selectedPieces.push(subModule); } if (subModule.active === false && index >= 0) { this.selectedPieces.splice(index, 1); } } } createTest(){ if(this.selectedPieces.length == 0){ this.errorMessage = "Please select zone"; } else if (this.pieceForm.valid){ const param ={ "pieceName":this.pieceForm.controls.nom.value, "selectedZoneList":this.selectedPieces } this.pieceManagement.saveZones(param).subscribe((response: any) => { this.commonService.openSnackBar('Piece have been added successfully!') this.dialogRef.close({event:'add'}); }); } } close(){ this.dialogRef.close({event:'cancel'}) } }
测试Spec文件
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AddPiecesComponent } from './add-pieces.component'; import { NO_ERRORS_SCHEMA } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { MatDialog, MatDialogRef } from "@angular/material/dialog"; import { FormsModule } from "@angular/forms"; import { CommonService } from 'src/app/shared/services/common.service'; import { PieceManagementService } from 'src/app/shared/services/piece-management/piece-management.service'; describe('AddPiecesComponent', () => { let component: AddPiecesComponent; let fixture: ComponentFixture<AddPiecesComponent>; beforeEach(() => { const formBuilderStub = () => ({ group: object => ({}) }); const matDialogStub = () => ({}); const matDialogRefStub = () => ({ close: object => ({}) }); const pieceManagementServiceStub = () => ({ fetchAllZones: () => ({ subscribe: f => f({}) }), saveZones: param => ({ subscribe: f => f({}) }) }); const commonServiceStub = () => ({ openSnackBar: string => ({}) }); TestBed.configureTestingModule({ imports: [FormsModule], schemas: [NO_ERRORS_SCHEMA], declarations: [AddPiecesComponent], providers: [ { provide: FormBuilder, useFactory: formBuilderStub }, { provide: MatDialog, useFactory: matDialogStub }, { provide: MatDialogRef, useFactory: matDialogRefStub }, { provide: PieceManagementService, useFactory: pieceManagementServiceStub }, { provide: CommonService, useFactory: commonServiceStub } ] }); fixture = TestBed.createComponent(AddPiecesComponent); component = fixture.componentInstance; }); it('can load instance', () => { expect(component).toBeTruthy(); }); it(`selectedPieces has default value`, () => { expect(component.selectedPieces).toEqual([]); }); it(`pieces has default value`, () => { expect(component.pieces).toEqual([]); }); describe('ngOnInit', () => { it('makes expected calls', () => { spyOn(component, 'createPiecesForm').and.callThrough(); spyOn(component, 'fetchAllZones').and.callThrough(); component.ngOnInit(); expect(component.createPiecesForm).toHaveBeenCalled(); expect(component.fetchAllZones).toHaveBeenCalled(); }); }); });
解决方案
要覆盖组件中所有方法的代码,需要针对每个方法编写独立测试用例,同时完善依赖服务的模拟逻辑,以下是具体步骤:
1. 完善FormBuilder模拟
当前的FormBuilder stub仅返回空对象,无法正确初始化表单,修改模拟逻辑:
// 在测试文件顶部导入FormGroup import { FormGroup, Validators } from '@angular/forms'; // 修改FormBuilder stub const formBuilderStub = () => ({ group: (controls) => new FormGroup(controls) });
2. 测试fetchAllZones方法
模拟服务返回真实数组,验证组件对响应的处理逻辑:
// 导入rxjs的of操作符 import { of } from 'rxjs'; describe('fetchAllZones', () => { it('should populate pieces array and set active to false for each item', () => { const mockZones = [{ zoneId: 1 }, { zoneId: 2 }]; const service = TestBed.inject(PieceManagementService); spyOn(service, 'fetchAllZones').and.returnValue(of(mockZones)); component.fetchAllZones(); expect(component.pieces.length).toBe(2); expect(component.pieces.every(item => item.active === false)).toBe(true); }); });
3. 测试activateClass方法的选择逻辑
覆盖选中、取消选中的分支场景:
describe('activateClass', () => { it('should add subModule to selectedPieces on first selection', () => { const subModule = { zoneId: 1, active: false, activeli: false }; component.activateClass(subModule); expect(subModule.active).toBe(true); expect(component.selectedPieces.length).toBe(1); }); it('should remove subModule from selectedPieces when deselected', () => { const subModule = { zoneId: 1, active: true, activeli: true }; component.selectedPieces.push(subModule); component.activateClass(subModule); expect(subModule.active).toBe(false); expect(component.selectedPieces.length).toBe(0); }); });
4. 测试createTest方法的分支逻辑
覆盖未选区域、表单无效、表单有效三种场景:
describe('createTest', () => { let service: PieceManagementService; let commonService: CommonService; let dialogRef: MatDialogRef<AddPiecesComponent>; beforeEach(() => { service = TestBed.inject(PieceManagementService); commonService = TestBed.inject(CommonService); dialogRef = TestBed.inject(MatDialogRef); component.createPiecesForm(); // 初始化表单 }); it('should set errorMessage when no zones are selected', () => { component.createTest(); expect(component.errorMessage).toBe('Please select zone'); }); it('should not call saveZones when form is invalid', () => { component.selectedPieces.push({ zoneId: 1 }); component.pieceForm.controls['nom'].setValue(''); const spy = spyOn(service, 'saveZones'); component.createTest(); expect(spy).not.toHaveBeenCalled(); }); it('should call saveZones and close dialog when form is valid', () => { const mockParam = { pieceName: 'Test', selectedZoneList: [{ zoneId: 1 }] }; component.selectedPieces.push(mockParam.selectedZoneList[0]); component.pieceForm.controls['nom'].setValue(mockParam.pieceName); const saveSpy = spyOn(service, 'saveZones').and.returnValue(of({})); const snackSpy = spyOn(commonService, 'openSnackBar'); const closeSpy = spyOn(dialogRef, 'close'); component.createTest(); expect(saveSpy).toHaveBeenCalledWith(mockParam); expect(snackSpy).toHaveBeenCalledWith('Piece have been added successfully!'); expect(closeSpy).toHaveBeenCalledWith({ event: 'add' }); }); });
5. 测试close方法
验证对话框关闭逻辑:
describe('close', () => { it('should close dialog with cancel event', () => { const dialogRef = TestBed.inject(MatDialogRef<AddPiecesComponent>); const spy = spyOn(dialogRef, 'close'); component.close(); expect(spy).toHaveBeenCalledWith({ event: 'cancel' }); }); });
6. 触发真实生命周期钩子
用fixture.detectChanges()触发ngOnInit,贴近真实组件初始化场景:
it('should trigger ngOnInit on component init', () => { spyOn(component, 'createPiecesForm'); spyOn(component, 'fetchAllZones'); fixture.detectChanges(); expect(component.createPiecesForm).toHaveBeenCalled(); expect(component.fetchAllZones).toHaveBeenCalled(); });
完成以上修改后,单元测试即可覆盖组件所有方法和逻辑分支。
内容的提问来源于stack exchange,提问作者tripti tiwari
相关产品推荐
相关产品推荐

