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

Angular单元测试中Ng-template始终为null的问题排查

问题描述

我想验证当dynamicValue为null/undefined时,组件会渲染ng-template中的内容;但单元测试里查找HTML中的ng-template元素时,始终返回null,导致测试失败。


相关代码

area-display-selection.component.html

<span data-testid="dynamic-value-span" *ngIf="dynamicValue; else noValue">
  {{configuration?.text | translate}}: {{dynamicValue}}
</span>
<ng-template data-testid="no-value-template" #noValue>
  {{configuration.nothingSelectedText | translate}}
</ng-template>

area-display-selection.component.ts

@Component({
  selector: 'analyse-area-display-selection',
  templateUrl: './area-display-selection.component.html',
  styleUrls: ['./area-display-selection.component.scss']
})
export class AreaDisplaySelectionComponent implements OnInit {

  private subscription: Subscription = new Subscription();
  public dynamicValue: string = undefined;
  @Input() configuration: SimpleSelectionConfig = undefined;

  constructor(private optionEventService: OptionEventService) { }

  ngOnInit(): void {
    this.subscription.add(
      this.optionEventService.getOptionEventByType(this.configuration.consumesOptionEvent)
        .subscribe((optionEvent: OptionEvent) => this.dynamicValue = optionEvent?.payload.value)
    );
  }

}

area-display-selection-component.spec.ts

describe('AreaDisplaySelectionComponent', () => {
  let fixture: ComponentFixture<AreaDisplaySelectionComponent>;
  let component: AreaDisplaySelectionComponent;
  let optionEventService: OptionEventService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        AnalyseModule,
        BrowserAnimationsModule
      ],
      providers: [TranslateStore, NoopAnimationPlayer, OptionEventService]
    }).compileComponents();
    fixture = TestBed.createComponent(AreaDisplaySelectionComponent);
    optionEventService = TestBed.inject(OptionEventService);
    component = fixture.componentInstance;
  });


  it('ng template', () => {
    const simpleSelectionConfig: SimpleSelectionConfig = {
        consumesOptionEvent: '',
        nothingSelectedText: '',
        text: 'mockSimpleSelectionConfig'
    };

    component.configuration = simpleSelectionConfig;
    fixture.detectChanges();

    const element = fixture.debugElement.query(By.css('ng-template'));
    expect(element).toBeTruthy(); // 此处测试失败,element返回null
  });

});

错误信息

测试断言expect(element).toBeTruthy()失败,element的值为null。


原因及解决方案

核心原因

ng-template是Angular的模板容器,它本身不会被渲染到最终的DOM结构中。只有当它通过else、ngTemplateOutlet等指令被引用时,Angular只会渲染模板内部的内容,不会保留<ng-template>标签。所以直接查找ng-template元素必然返回null。

正确测试方案

单元测试的核心是验证组件的渲染结果,而非模板标签本身,以下是两种场景的测试方法:

1. 测试无值状态(dynamicValue为undefined)

当dynamicValue未定义时,组件会渲染noValue模板的内容,需验证内容是否正确显示:

it('should display nothing selected text when dynamicValue is undefined', () => {
  // 准备测试数据
  const testNothingText = '未选择任何内容';
  const testConfig: SimpleSelectionConfig = {
    consumesOptionEvent: '',
    nothingSelectedText: testNothingText,
    text: '测试标题'
  };

  component.configuration = testConfig;
  fixture.detectChanges();

  // 步骤1:先修改HTML,把data-testid移到模板内的元素上(否则无法直接定位)
  // 修改后的ng-template:
  // <ng-template #noValue><span data-testid="no-value-content">{{configuration.nothingSelectedText | translate}}</span></ng-template>
  
  // 查找渲染后的内容元素
  const noValueElement = fixture.debugElement.query(By.css('[data-testid="no-value-content"]'));
  expect(noValueElement).toBeTruthy();
  expect(noValueElement.nativeElement.textContent.trim()).toBe(testNothingText);

  // 同时验证动态值span不显示
  const dynamicSpan = fixture.debugElement.query(By.css('[data-testid="dynamic-value-span"]'));
  expect(dynamicSpan).toBeFalsy();
});

2. 测试有值状态(dynamicValue不为null)

当dynamicValue赋值后,组件应显示动态值的span,隐藏模板内容:

it('should display dynamic value when dynamicValue is set', () => {
  const testTitle = '测试标题';
  const testValue = '北京';
  const testConfig: SimpleSelectionConfig = {
    consumesOptionEvent: '',
    nothingSelectedText: '未选择任何内容',
    text: testTitle
  };

  component.configuration = testConfig;
  component.dynamicValue = testValue;
  fixture.detectChanges();

  // 验证动态值span存在且内容正确
  const dynamicSpan = fixture.debugElement.query(By.css('[data-testid="dynamic-value-span"]'));
  expect(dynamicSpan).toBeTruthy();
  expect(dynamicSpan.nativeElement.textContent.trim()).toBe(`${testTitle}: ${testValue}`);

  // 验证模板内容不显示
  const noValueElement = fixture.debugElement.query(By.css('[data-testid="no-value-content"]'));
  expect(noValueElement).toBeFalsy();
});

3. (可选)验证模板引用存在

如果只是想确认组件中定义了noValue模板,可以通过@ViewChild获取模板引用验证:

// 先在组件类中添加模板引用声明
// @ViewChild('noValue') noValueTemplate: TemplateRef<any>;

it('should have noValue template defined', () => {
  fixture.detectChanges();
  expect(component.noValueTemplate).toBeTruthy();
});

内容的提问来源于stack exchange,提问作者Ruttzn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 03:15:42