ionic 单元测试

Angular项目创建时,会自动生成spec.ts单元测试文件. 在ionic项目中, 需要手动创建spec.ts文件和添加依赖的框架.

添加配置依赖可以参看ionic-team写的一篇文章 :点我😄

单元测试基础入门

简单示例
describe('A suite is just function',()=>{
  var a;
  it('and so is a spec ', function () {
    a = true;
    expect(a).toBe(true);
  });
})

执行结果:


a 等于 true, 用例测试成功

上面的例子是一个很简单的单元测试, 它有三部分组成: describe() , it()expect()组成;

编写单元测试用例的基本步骤:
  • 设计测试用例函数: 根据测试场景组合使用describe()it()来定义测试用例;
  • 编写判断逻辑: 在it()函数中, 需要使用expect()断言函数来判定用例测试结果. 例如, expect(a).toBe(true);表示变量a应等于true,否则说明这个用例测试失败.
    a 等于 false, 则用例测试失败
测试集

Jasmine中, 将功能相似的测试用例统一聚集在测试集(Suites)中, 并通过describe()函数标识. 假设被测试的业务类有多个函数, 则需要编写多个测试用例, 并将这些测试用例都放在同个 describe()函数中.

测试点

每个具体的功能测试点 (Spec) 可以用全局函数it()来定义, 该函数的第一个参数表示该用例的名称, 第二个参数为测试的细节. it()函数可以包含一个或者多个断言 (expect), 每个断言的结果只能是 true或者false, 只有当所有断言都为true是, 该测试点才算通过.

内置匹配器

内置匹配器(Matchers), 就是更在expect()函数后面, 用于判断结果是否符合期望值的函数. 在上面的例子中, 已经使用了一个很常用的匹配器toBe().

内置常用匹配器

  • toBe()

toBe()本质是使用操作符 ===来比较结果值和期望值, not.toBe()则表示不等, 示例代码如下:

  it("The 'toBe()' matcher compares with '===' ", function () {
    var a = 15;
    var b = a;
    expect(a).toBe(b);
    expect(a).not.toBe(null);
  });
  • toEqual()
    toEqual()用于比较两个对象、数字、字符串等, 示例代码如下:
  it('should work for objects', function () {
    var foo = {
      a: 8,
      b: 15
    };

    var bar = {
      a: 8,
      b: 15
    }

    expect(foo).toEqual(bar);
  });
  • toMatch()
    toMatch()用于验证是否匹配正则表达式, 示例代码如下:
  it("The 'toMatch' matcher is for regular expressions", function () {
    var message = 'Ionic CLI and Cordova';
    expect(message).toMatch(/ionic/i);
  });
  • toContain()
    toContain()用于验证数组是否包含指定元素, 示例代码如下:
  it("The 'toContain' matcher is for finding an item in an array", function () {
    var array = ['React', 'Vue', 'Angular'];
    expect(array).toContain('Angular');
  });
Jasmine 内置匹配器:
匹配器 作用
toBe 使用 === 比较结果
toEqual 比较两个对象、数字、字符串等是否相等
toMatch 正则表达式匹配
toBeNull 验证是否为null
toBeTruthy 验证是否为 true
toBeFalsy 验证是否为 false
toBeLessThan 验证结果是否小于指定值
toBeGreaterThan 验证结果是否大于指定值
toContain 验证数组是否包含指定元素
toBeCloseTo 将值进行四舍五入后比较是否相等
toThrow 验证函数是否抛出一个错误
toThrowError 验证函数是否抛出指定的错误
toBeDefined 验证对象是否 不为 undefined
beforeEach 、afterEach函数

另外, 在测试中还会经常用到 beforeEach()afterEach() 这两个函数, 用于定义每个测试用例执行前及执行后的公共逻辑 。

  • beforeEach() : 定义了每个用例在执行前, 所需要执行的初始化函数。
  • afterEach (): 每个用例在执行结束后, 均将执行afterEach ()函数。

beforeEach()的简单使用:

describe('How to use beforeEach', () => {

  let foo, bar;
  beforeEach(() => {
    foo = {
      a: 8,
      b: 15
    };

    bar = {
      a: 8,
      b: 15
    }
  })

  it('should work for objects', function () {
    expect(foo).toEqual(bar);
  });
})

单元测试

  • 组件的测试
    page3.html:
    <ion-content no-padding>
      <ion-list>
        <button ion-item *ngFor="let item of list">
          {{item}}
        </button>
      </ion-list>
    </ion-content>
    
    page3.ts:
    export class Page3 {
    
      list: string[] = ['张三', '李四', '王五'];
    
      constructor() {}
      ionViewDidLoad() {}
    
      addItem() {
        this.list.push(`赵六`);
      }
    }
    
    page3.spec.ts
    import {Page3} from "./page3";
    
    describe('Page3 组件', () => {
      let component;
      beforeEach(() => {
        component = new Page3();
      });
    
      it("调用 addItem() 函数, 并检测该函数是否添加了 '赵六'", function() {
        component.addItem();
        expect(component.list).toContain('赵六');
      });
    })
    
  • 管道的测试
    要求: 创建一个pipe, 把手机号(15912345678)显示为159-1234-5678的形式,并对该pipe做单元测试。
    ll-phone.ts
    import {Pipe, PipeTransform} from '@angular/core';
    
    @Pipe({
      name: 'llPhone',
    })
    export class LlPhonePipe implements PipeTransform {
    
      transform(value: string): string {
        if (!value) return '';
        if (value.length === 11) {
          return value.replace(/(\d{3})(\d{4})(\d{4})/, (m, m1, m2, m3) =>     {
            return [m1, m2, m3].join('-');
          })
        } else {
          return value;
        }
      }
    }
    
    ll-phone.spec.ts:
    import {LlPhonePipe} from "./ll-phone";
    
    describe('测试 llPhonePipe', () => {
    
      let phonePipe: LlPhonePipe;
    
      beforeEach(() => {
        phonePipe = new LlPhonePipe();
      });
    
      it('should transforms phone', function () {
        expect(phonePipe.transform('15912345678')).toEqual('159-1234-5678');
      });
    })
    
  • 服务的测试
    创建一个MockDataLlMockDataProvider
    ll-mock-data.ts:
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class LlMockDataProvider {
      constructor() {}
      public hotMenu = [{
        "img": "http://file.j1home.com/%E5%AE%89%E5%85%A8%E7%A4%BE%E5%8C%BA@2x.png",
        "name": "社区安全"
      }, {
        "img": "http://file.j1home.com/%E7%A4%BE%E5%8C%BA%E5%85%9A%E5%BB%BA@2x.png",
        "name": "社区党建"
      }];
    }
    
    ll-mock-data.spec.ts:
    import {inject, TestBed} from "@angular/core/testing";
    import {LlMockDataProvider} from "./ll-mock-data";
    
    describe('test provider', () => {
      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [LlMockDataProvider]
        });
      });
    
      it('should contain item', inject([LlMockDataProvider], (service) => {
        expect(service.hotMenu[1]['name']).toEqual('社区党建');
      }));
    })
    

通过TestBed注入被测类

在上面的代码ll-mock-data.spec.ts中,我们使用到了TestBed.configureTestingModule(),它为我们创建了一个测试 NgModule,在它的元数据里面,我们可以注入被测试的类。如果有依赖,比如:Http,那么你需要 imports: [HttpModule],这一切都跟 NgModule 写法完全一置。

beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [LlMockDataProvider]
    });
});

组件 DOM 交互测试

在下面的图片中, 如果想检测DOM元素的内容是否符合我们的要求,
可通过TestBed进行组件的初始化, 可以获取组件模板对应的DOM元素以及检查 DOM元素的内容等。

示例代码如下:
page3.html

<ion-content no-padding>
  <ion-list>
    <button ion-item *ngFor="let item of list">
      {{item | llPhone}}
    </button>
  </ion-list>
</ion-content>

page3.ts

import {Component} from '@angular/core';
import {IonicPage, NavController, NavParams} from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-page3',
  templateUrl: 'page3.html',
})
export class Page3 {
  list: string[] = ['15512345678'];
  constructor() {}
  ionViewDidLoad() {}
  addItem() {
    this.list.push(`15512345678`);
  }
}

page3.spec.ts

import {TestBed} from "@angular/core/testing";
import {Page3} from "./page3";
import {IonicModule} from "ionic-angular";
import {LlPhonePipe} from "../../pipes/ll-phone/ll-phone";

describe('Page3 组件', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [Page3, LlPhonePipe],
      imports: [
        IonicModule.forRoot(Page3)
      ],
    });
  });

  it('test simple component with TestComponentBuilder', () => {
    var fixture = TestBed.createComponent(Page3);
    var compiled = fixture.debugElement.nativeElement;

    // 调用 组件的 addItem() 函数
    fixture.componentInstance.addItem();
    // 变化监测
    fixture.detectChanges();

    // replace(/(^\s*)|(\s*$)/g, "") 移除字符串首尾空格
    let val = compiled.querySelector('.label').textContent.replace(/(^\s*)|(\s*$)/g, "");
    expect(val).toBe('155-1234-5678');
  });
});

在上面的代码中, 需要关注以下的知识点:

  • TestBedcreateComponent()方法用于初始化被测组件, 并创建 ComponentFixture对象, 这个对象可以认为是被测组件的上下文环境, 通过它可以获取已经完成初始化的组件实例以及DOM元素等。
  • fixture.detectChanges()方法用于当JavaScript变量及模板内容变更时触发变化监测。组件初始化、DOM元素或JavaScript值变化时,都需要调用这个方法, 以便触发 Angular的变化监测机制。
  • fixture.debugElement.nativeElement用于获取组件对应的原生DOM元素, 之后可以通过querySelector()DOM元素原生 API做进一步处理。
  • fixture.componentInstance用于获取组件对应的对象实例,上面代码中通过fixture.componentInstance调用该实例对象的addItem()函数: fixture.componentInstance.addItem();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 这三天我分别写了关于未来职业的发展趋势,看完文章之后,大家可以重新思考自己的底层工作方式。 三个维度提升你的职业身...
    123涅槃阅读 415评论 14 8
  • 终日不锻炼的同学,估计很难体会到,健身人士的那些“不良癖好”,健身人士也是要上班工作的,只有少数的人拿健身当做工作...
    九鱼亭阅读 1,752评论 0 0
  • 张远兮本想着开年了,应该会转转运吧,即使不转运总会有点新气象。结果人生不如意不是八九,而是十打十的十。 一早上起来...
    杨耿耿阅读 157评论 0 0