You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为带@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 00:31:02