Angular 单元测试实践 (4)

本文继续介绍如何对 Angular 的管道(pipe)、指令(directive)和表单(form)进行单元测试。

测试管道

Angular 的指令是一个实现了 PipeTransform 接口的 TypeScript 类,暴露了 transform 方法。指令操作通常是同步的,很少与 DOM 元素进行交互,也几乎没有什么依赖。因此,对指令的单元测试也比较简单,甚至不需要 Angular 的测试工具类。

考虑这样一个管道 CommaSeperatorPipe:把逗号分割的字符串转换成一个列表:

import { Pipe, PipeTransform } from '@angular/core’;

@Pipe({
  name: ‘commaSeperator’
})
export class CommaSeperatorPipe implements PipeTransform {

  transform(value: string): string[] {
    return value.split(',’);
  }

}

测试代码只需要实例化一个管道对象,向 transform 方法传入一些模拟的数据,验证该方法转换的结果即可:

import { CommaSeperatorPipe } from './comma-seperator.pipe’;

describe('CommaSeperatorPipe', () => {
  it('create an instance', () => {
    const pipe = new CommaSeperatorPipe();
    expect(pipe).toBeTruthy();
  });

  it('should return an array', () => {
    const pipe = new CommaSeperatorPipe();
    expect(pipe.transform('1,2,3,4,5')).toEqual(['1', '2', '3', '4', '5’]);
  });
});

测试报表页面:

管道测试报表页面

测试指令

指令通常用来改变一个元素、组件或另一个指令的行为。指令一般和组件一起协同工作,没有外部的依赖。

考虑这样一个指令 HighlightDirective :基于绑定的数据值,设置元素的背景颜色:

import { Directive, ElementRef, Input, OnChanges } from '@angular/core’;

@Directive({ selector: '[highlight]’ })
/**
 * 设置宿主元素的背景颜色
 */
export class HighlightDirective implements OnChanges {

  defaultColor =  'rgb(211, 211, 211)'; // 默认为浅灰色

  @Input('highlight') bgColor = ‘’;

  constructor(private el: ElementRef) {
    el.nativeElement.style.customProperty = true;
  }

  ngOnChanges() {
    this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
  }
}

在下面 TestHost 这个组件里,测试 HighlightDirective 指令:

@Component({
  template: `<h2 highlight="skyblue">国学</h2>
  <h3>今日经典:</h3>
  <q>学而时习之,不亦乐乎。</q>`
})
class TestHostComponent {}

现在就可以写单元测试,验证 h2 元素是否添加了 highlight 指令,并设置了正确的背景颜色。

describe('HighlightDirective', () => {
  let container: HTMLElement;

  beforeEach(() => {
    const fixture = TestBed.configureTestingModule({
      declarations: [ TestHostComponent, HighlightDirective ]
    })
    .createComponent(TestHostComponent);
    fixture.detectChanges();
    container = fixture.nativeElement.querySelector('h2’);
  });

  it('should have skyblue <h2>', () => {
    const bgColor = container.style.backgroundColor;
    expect(bgColor).toBe('skyblue’);
  });

测试报表页面:

指令测试报表页面

测试表单

表单是 Angular 应用的重要组成部分,很少有应用不包含一个表单的。我们使用下面的查询表单,介绍如何对表单进行单元测试。

<form [formGroup]="searchForm" (ngSubmit)="search()”>
  <input type="text" placeholder="请输入用户名" formControlName=“searchText”>
  <button type="submit" [disabled]="searchForm.invalid">查询</button>
</form>

组件类定义如下:

import { Component, OnInit } from '@angular/core’;
import { FormControl, FormGroup, Validators } from '@angular/forms’;

@Component({
  selector: 'app-search’,
  templateUrl: './search.component.html’,
  styleUrls: ['./search.component.css’]
})
export class SearchComponent implements OnInit {

  get searchText(): FormControl {
    return this.searchForm.controls.searchText as FormControl;
  }

  searchForm = new FormGroup({
    searchText: new FormControl('', Validators.required)
  });

  search() {
    if(this.searchForm.valid) {
      console.log(‘查询内容是: ' + this.searchText.value)
    }
  }
  constructor() { }

  ngOnInit(): void {
  }

}

这里,我们要测试三个地方:

  1. searchText 属性的设置是否正确
  2. 查询 按钮禁用状态是否正确
  3. console.log 是否调用正确

编写测试用例如下:

describe('SearchComponent', () => {
  let component: SearchComponent;
  let button: HTMLButtonElement;
  let fixture: ComponentFixture<SearchComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ SearchComponent ],
      imports: [ReactiveFormsModule]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(SearchComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    button = fixture.nativeElement.querySelector('button’);
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should set the searchText', () => {
    const input: HTMLInputElement = fixture.nativeElement.querySelector('input’);
    input.value = ‘Angular’;
    input.dispatchEvent(newEvent('input’));
    expect(component.searchText.value).toBe('Angular’);
  });

  it('should disable search button', () => {
    component.searchText.setValue(‘’);
    expect(button.disabled).toBeTrue();
  });

  it('should log to the console', () => {
    const spy = spyOn(console, 'log’);
    component.searchText.setValue('Angular’);
    fixture.detectChanges();
    button.click();
    expect(spy.calls.first().args[0]).toBe('查询内容是: Angular’);
  });
});

在第一个测试用例中,使用 nativeElement 属性的 querySelector 方法,定位到 input
元素,并设置该元素的值为 Angular. 接着,调用 input 元素的 dispatchEvent 方法,通知 Angular 框架,input 元素的值已经发生变化。最后,验证组件类的 searchText 属性值是否接收到了 input 元素的值。

在第二个测试用例中,首先,设置 input 元素的值为空。然后,验证查询按钮处于禁用状态。

在第三个测试用例中,首先,设置 input 元素的值为 Angular。然后,触发变化通知。接着,模拟按钮点击操作。最后,使用 Spying 方法,对输出到控制台到内容进行验证。

测试报表页面:

表单测试报表页面
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容