如何为带@Host()的Angular指令编写单元测试以实现全代码覆盖
问题
我有一个Angular指令,会在输入控件上调用forceErrorStateUpdate方法。如何对以下代码进行单元测试以实现全代码覆盖?目前的问题是,我无法覆盖ngOnInit中调用this._textInput?.forceErrorStateUpdate();的代码行,当前测试覆盖率约为75%。
指令代码:
import { Directive, Host, OnInit, Optional } from '@angular/core'; @Directive({ selector: '[showValidationError]' }) export class TestDirective implements OnInit { constructor(@Host() @Optional() private _textInput: any) { } ngOnInit(): void { this._textInput?.forceErrorStateUpdate(); } }
现有单元测试代码(覆盖率约75%):
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TestComponent } from '../test/test.component'; import { TestDirective } from './test.directive'; describe('TestDirective', () => { let testComponent: TestComponent; let testFixture: ComponentFixture<TestComponent>; let directiveInstance: TestDirective; beforeEach(() => { TestBed.configureTestingModule({ imports: [], declarations: [TestComponent, TestDirective] }).compileComponents(); }); beforeEach(() => { testFixture = TestBed.createComponent(TestComponent); testComponent = testFixture.componentInstance; testFixture.detectChanges(); const directiveEl = testFixture.debugElement.query(By.directive(TestDirective)); expect(directiveEl).not.toBeNull(); directiveInstance = directiveEl.injector.get(TestDirective); }); it('should create', () => { expect(directiveInstance).toBeTruthy(); }); it('should have ngOnInit', () => { expect(directiveInstance.ngOnInit).toBeDefined(); }); it('should invoke forceErrorUpdate on input component', () => { directiveInstance.ngOnInit(); expect(directiveInstance.ngOnInit).toBeDefined(); }); });
解决方案
要覆盖ngOnInit中的目标代码行,需要分别测试宿主输入组件存在和宿主输入组件不存在两种场景,覆盖可选参数的分支逻辑:
1. 修改测试代码,覆盖所有分支
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Directive, Host, Optional, OnInit, Component } from '@angular/core'; // 模拟带有forceErrorStateUpdate方法的输入组件 @Component({ template: '<div showValidationError></div>' }) class MockTextInputComponent { // 使用Jasmine Spy跟踪方法调用 forceErrorStateUpdate = jasmine.createSpy('forceErrorStateUpdate'); } @Directive({ selector: '[showValidationError]' }) export class TestDirective implements OnInit { constructor(@Host() @Optional() private _textInput: any) { } ngOnInit(): void { this._textInput?.forceErrorStateUpdate(); } } describe('TestDirective', () => { let fixture: ComponentFixture<MockTextInputComponent>; let mockInputComponent: MockTextInputComponent; let directive: TestDirective; beforeEach(() => { TestBed.configureTestingModule({ declarations: [MockTextInputComponent, TestDirective] }); fixture = TestBed.createComponent(MockTextInputComponent); mockInputComponent = fixture.componentInstance; // 获取指令实例 const directiveEl = fixture.debugElement.query(By.directive(TestDirective)); directive = directiveEl.injector.get(TestDirective); fixture.detectChanges(); // 触发ngOnInit生命周期 }); it('should create the directive', () => { expect(directive).toBeTruthy(); }); it('should call forceErrorStateUpdate when host input component exists', () => { // 验证方法被调用 expect(mockInputComponent.forceErrorStateUpdate).toHaveBeenCalled(); }); it('should not throw error when host input component is missing', () => { // 创建不带宿主输入组件的测试组件 @Component({ template: '<div showValidationError></div>' }) class NoHostComponent {} TestBed.overrideComponent(NoHostComponent, { set: { declarations: [TestDirective] } }); const noHostFixture = TestBed.createComponent(NoHostComponent); // 验证执行ngOnInit时无报错 expect(() => noHostFixture.detectChanges()).not.toThrow(); }); });
关键修改说明
- 模拟宿主组件:创建带Spy方法的模拟输入组件,用于跟踪
forceErrorStateUpdate的调用情况。 - 分场景验证:
- 宿主组件存在时,验证目标方法被调用。
- 宿主组件不存在时,确保指令执行不会抛出错误(覆盖可选参数为null的分支)。
- 触发真实生命周期:通过
fixture.detectChanges()触发ngOnInit,而非手动调用方法,更贴近实际运行场景。
修改后即可覆盖指令中所有代码分支,实现100%代码覆盖率。
内容的提问来源于stack exchange,提问作者dvln
相关产品推荐
相关产品推荐

