Angular 组件Dom测试

本文主要探讨Angular组件的DOM测试,包括使用`createComponent()`创建组件实例,通过ComponentFixture进行交互,利用`nativeElement`访问原生元素,以及借助DebugElement和By.css()进行测试辅助。测试中,我们关注组件的渲染、用户交互响应以及与其他组件的集成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

组件Dom测试

组件不仅仅是一个类,它还会与 DOM 以及其它组件进行交互。我们要知道组件是否能正确渲染、是否响应用户输入和手势,或者是否集成到它的父组件和子组件中,我们需要借助TestBed的其它特性和其它的测试辅助函数。

describe('BannerComponent (with beforeEach)', () => {
  let component: BannerComponent; // 要测试的组件BannerComponent
  let fixture: ComponentFixture<BannerComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
    	// 声明要测试的组件
    	declarations: [BannerComponent]
    });
    
    // TestBed.createComponent 创建BannerComponent组件实例,返回一个`ComponentFixture` 对象
    fixture = TestBed.createComponent(BannerComponent);

    // ComponentFixture.componentInstance 拿到 BannerComponent实例
    component = fixture.componentInstance;
  });

  it('should create', () => {
    // 断言组件被定义了
    expect(component).toBeDefined();
  });

  // ComponentFixture.nativeElement 获取组件的元素,并查找预期的文本。
  it('should contain "banner works!"', () => {
    const bannerElement: HTMLElement = fixture.nativeElement;
    expect(bannerElement.textContent).toContain('banner works!');
  });
});

createComponent()

TestBed.createComponent() 会创建 BannerComponent 的实例,它把一个对应元素添加到了测试运行器的 DOM 中,并返回一个ComponentFixture 对象。

const fixture: ComponentFixture<BannerComponent> = TestBed.createComponent(BannerComponent);

ComponentFixture

ComponentFixture 是一个测试挽具,用于与所创建的组件及其对应的元素进行交互。
可以通过测试夹具(fixture)访问组件实例,并用 Jasmine 的期望断言来确认它是否存在:

const component = fixture.componentInstance;
expect(component).toBeDefined();

ComponentFixture类提供了很多特别有用的属性和方法供我们在测试中使用。

class ComponentFixture<T> {
  constructor(componentRef: ComponentRef<T>, ngZone: NgZone, _autoDetect: boolean)

  // 与该组件的根元素关联的 DebugElement。【常用】
  debugElement: DebugElement

  // 根组件类的实例。【常用】
  componentInstance: T
  
  // 组件根部的原生元素。
  nativeElement: any

  // 组件的根部元素的ElementRef
  elementRef: ElementRef

  // 组件的 ChangeDetectorRef
  changeDetectorRef: ChangeDetectorRef
  componentRef: ComponentRef<T>
  ngZone: NgZone | null

  // 触发组件的变更检测周期。【常用】
  detectChanges(checkNoChanges: boolean = true): void
   
  // 进行变更检测以确保没有更改。
  checkNoChanges(): void

  // 设置夹具(fixture)是否应自动检测变化。(还运行一次detectChanges,以便检测到任何现有的更改。)
  autoDetectChanges(autoDetect: boolean = true)

  // 返回此夹具(fixture)当前是否稳定或者是否具有尚未完成的异步任务。
  isStable(): boolean

  // Get a promise that resolves when the fixture is stable.
  // 返回值类型:Promise<any>。
  // 当事件已触发异步活动或异步变更检测后,可用此方法继续执行测试。
  whenStable(): Promise<any>

  // Get a promise that resolves when the ui state is stable following animations.
  whenRenderingDone(): Promise<any>

  // 触发组件的销毁。
  destroy(): void
}

nativeElement

ComponentFixture.nativeElement 用于获取组件根部的原生元素。 当它的类型是HTMLElement 或其派生类之一时,我们可以用标准的 HTML querySelector 深入了解元素树。

it('should have <p> with "banner works!"', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  const p = bannerElement.querySelector('p')!;
  expect(p.textContent).toEqual('banner works!');
});

DebugElement

nativeElement的属性依赖于其运行时环境。在非浏览器平台上运行这些测试时,那些平台上可能没有 DOM,或者其模拟的 DOM 不支持完整的 HTMLElement API。

Angular 依靠 DebugElement抽象来在其支持的所有平台上安全地工作。 Angular 不会创建 HTML 元素树,而会创建一个 DebugElement 树来封装运行时平台上的原生元素。使用nativeElement 属性解包(unwrap) DebugElement 并返回特定于平台的元素对象。

const bannerElement: HTMLElement = fixture.nativeElement;

// 上面👆🏻这行代码的最终实现:
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;

DebugElement 还有另一些在测试中很有用的方法和属性:

class DebugElement extends DebugNode {

  // 元素的标签名 (如果是一个元素的话)
  name: string

  // 元素的属性名称到属性值的映射。
  // 它包括:常规属性绑定(例如 [id]="id")、主机属性绑定(例如主机:{'[id]':“id”})、内插属性绑定(例如`id="{{ value }}")
  // 它不包括:输入属性绑定(例如 [myCustomInput]="value")、属性绑定(例如 [attr.role]="menu")
  // property着重强调具有多少参数,或事物拥有的参数,不用它来特指某事物,只强调“有”。
  properties: {[key: string]: any}

  // 元素的属性名称到属性值的映射。
  // attribute 着重强调某个事物固有的属性,或区别于其他事物的特征,强调“专”。
  attributes: {[key: string]: string|null}
  
  // 包含元素上的类名作为键的映射。此映射派生自 DOM 元素的 className 属性。
  // 注意:此对象的值将始终为真。 如果元素上不存在类键,则该类键不会出现在 KV 对象中。
  classes: {[key: string]: boolean}
 
  // DOM元素的内联样式。如果底层 DOM 元素上没有样式属性,则为 null。
  styles: {[key: string]: string | null;}

  // DOM元素的childNodes,是一个DebugNode数组。
  childNodes: DebugNode[]

  // 组件根部的底层 DOM 元素。
  nativeElement: any

  // The immediate DebugElement children. Walk the tree by descending through children.
  children: DebugElement[]

  // 返回匹配的第一个DebugElement元素
  query(predicate: Predicate<DebugElement>): DebugElement

  // 返回所有匹配的DebugElement元素
  queryAll(predicate: Predicate<DebugElement>): DebugElement[]

  // 回所有匹配的DebugNode
  queryAllNodes(predicate: Predicate<DebugNode>): DebugNode[]
   
  // 如果元素的监听器集合中有相应的监听器,则按其名称触发事件。
  triggerEventHandler(eventName: string, eventObj: any): void

  // 继承自 core/DebugNode
  listeners: DebugEventListener[]
  
  parent: DebugElement | null
  
  nativeNode: any

  // 示例:https://angular.cn/guide/testing-attribute-directives
  injector: Injector
  
  componentInstance: any
  
  context: any
  
  references: {...}
  
  providerTokens: any[]
}

By.css()

由于有些应用可能至少要在某些时候运行在不同的平台上。

例如,作为优化策略的一部分,该组件可能会首先在服务器上渲染,以便在连接不良的设备上更快地启动本应用。服务器端渲染器可能不支持完整的 HTML 元素 API。如果它不支持 querySelector,之前的测试就会失败。

DebugElement 提供了适用于其支持的所有平台的查询方法。这些查询方法接受一个predicate函数,当 DebugElement 树中的一个节点与选择条件匹配时,该函数返回 true。

我们可以借助从库中为运行时平台导入By 类来创建一个predicate。这里的 By 是从浏览器平台导入的。

// By 是与 DebugElement 的查询功能一起使用的predicate。
class By {

  // 匹配所有节点nodes。  比如 debugElement.query(By.all());
  static all(): Predicate<DebugNode>
   
  // 通过给定的 CSS 选择器匹配元素。比如 debugElement.query(By.css('[attribute]'));
  static css(selector: string): Predicate<DebugElement>
   
  // 匹配存在给定指令(或组件)的节点。
  // 比如 debugElement.query(By.directive(MyDirective));
  // 比如 debugElement.query(By.directive(MyComponent));
  static directive(type: Type<any>): Predicate<DebugNode>
}

使用By.css()的测试示例:

import { By } from '@angular/platform-browser';

it('should find the <p> with fixture.debugElement.query(By.css)', () => {
  const bannerDe: DebugElement = fixture.debugElement;

  // 静态方法By.css()会用标准的CSS选择器来选择DebugElement中的各个节点。该查询会返回一个DebugElement。
  const paragraphDe = bannerDe.query(By.css('p'));
  
  // 需要使用nativeElement属性解包(unwrap)上面的返回结果才能得到 p 元素。
  const p: HTMLElement = paragraphDe.nativeElement;
  
  expect(p.textContent).toEqual('banner works!');
});

当你使用 CSS 选择器进行过滤并且只测试浏览器原生元素的属性时,用 By.css 方法可能会有点过度。用 HTMLElement 方法(比如 querySelector()querySelectorAll())进行过滤通常更简单,更清晰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值