angular单元测试(二)

作者:仔奶昔   发布日期:6 年前

人气4390
 

Karma配置

让我们来看看由angular-cli创建的karma配置文件。

// Karma configuration file, see link for more information

// https://karma-runner.github.io/1.0/config/configuration-file.html


module.exports = function (config) {

  config.set({

    basePath: '',

    frameworks: ['jasmine', '@angular-devkit/build-angular'],

    plugins: [

      require('karma-jasmine'),

      require('karma-chrome-launcher'),

      require('karma-jasmine-html-reporter'),

      require('karma-coverage-istanbul-reporter'),

      require('@angular-devkit/build-angular/plugins/karma')

    ],

    client: {

      clearContext: false // leave Jasmine Spec Runner output visible in browser

    },

    coverageIstanbulReporter: {

      dir: require('path').join(__dirname, '../coverage'),

      reports: ['html', 'lcovonly'],

      fixWebpackSourcePaths: true

    },

    reporters: ['progress', 'kjhtml'],

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: true,

    browsers: ['Chrome'],

    singleRun: false

  });

};

你大概可以猜到这些配置属性的大部分用途,但我们来看看其中的一些。


frameworks:这是jasmine被设定为测试框架的地方。如果你想使用另一个框架,这是做这件事的地方。

reporters:负责将测试结果告知给开发者。通常是将结果打印到控制台上,或者存入文件中

autoWatch:如果设置为true,则测试将以Watch模式运行。如果您更改任何测试并保存文件,测试将重新生成并重新运行。

browsers:这是您设置测试应该运行的浏览器的位置。默认情况下是chrome,但你可以安装和使用其他浏览器启动器。

Test.ts文件

karma的angular-cli配置使用文件“test.ts”作为应用程序测试的入口点。我们来看看这个文件;

// This file is required by karma.conf.js and loads recursively all the .spec and framework files


import 'zone.js/dist/zone-testing';

import { getTestBed } from '@angular/core/testing';

import {

  BrowserDynamicTestingModule,

  platformBrowserDynamicTesting

} from '@angular/platform-browser-dynamic/testing';


declare const require: any;


// First, initialize the Angular testing environment.

getTestBed().initTestEnvironment(

  BrowserDynamicTestingModule,

  platformBrowserDynamicTesting()

);

// Then we find all the tests.

const context = require.context('./', true, /\.spec\.ts$/);

// And load the modules.

context.keys().map(context);

你可能永远不需要改变这个文件,但是有时候还是会更改的,比如:某一个spec文件排除测试等。

测试体验

我们来创建我们的第一个测试。修改app.component.ts。这个组件只有一个属性“text”,其值为“Angular Unit Testing”,它是在HTML中的“h1”标记中呈现的,它还包含路由根元素和一些路由链接。让我们创建一个测试文件来检查组件是否实际具有该属性,并且实际上是在HTML中呈现的。

app.component.html文件

<h1>{{text}}</h1>

<router-outlet></router-outlet>

app.component.ts文件

import { Component } from '@angular/core';


@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.css']

})

export class AppComponent {

  text = 'Angular Unit Testing';

}

app.component.spec.ts文件

import { TestBed, async } from '@angular/core/testing';

import { RouterTestingModule } from '@angular/router/testing';

import { AppComponent } from './app.component';

describe('AppComponent', () => {

  beforeEach(async(() => {

    TestBed.configureTestingModule({

      imports: [

        RouterTestingModule

      ],

      declarations: [

        AppComponent

      ],

    }).compileComponents();

  }));

  it('should create the app', async(() => {

    const fixture = TestBed.createComponent(AppComponent);

    const app = fixture.debugElement.componentInstance;

    expect(app).toBeTruthy();

  }));

  it(`should have as title 'app'`, async(() => {

    const fixture = TestBed.createComponent(AppComponent);

    const app = fixture.debugElement.componentInstance;

    expect(app.text).toEqual('Angular Unit Testing');

  }));

  it('should render title in a h1 tag', async(() => {

    const fixture = TestBed.createComponent(AppComponent);

    fixture.detectChanges();

    const compiled = fixture.debugElement.nativeElement;

    expect(compiled.querySelector('h1').textContent).toContain('Welcome to Angular Unit Testing!');

  }));

});

此时执行:

ng test

npm test

ng test的常用参数. 

- –code-coverage -cc 代码覆盖率报告, 默认这个是不开启的, 因为生成报告的速度还是比较慢的. 

- –colors 输出结果使用各种颜色 默认开启 

- –single-run -sr 执行测试, 但是不检测文件变化 默认不开启 

- –progress 把测试的过程输出到控制台 默认开启 

- –sourcemaps -sm 生成sourcemaps 默认开启 

- –watch -w 运行测试一次, 并且检测变化 默认开启

在弹出的chrome浏览器窗口中显示:

image

我们详细介绍一下这个测试代码

导入测试文件的所有依赖项 
这里要注意,你在组件内使用的依赖,这里面同样需要导入,否则会无法运行。

使用describe开始我们的测试

describe是一个函数,Jasmine 就是使用 describe 全局函数来测试的。

declare function describe(description: string, specDefinitions: () => void): void;

表示分组类似测试套,也就是一组测试用例,支持description嵌套。

例子:

describe('测试显示/隐藏筛选条件', ()=>{  

})

我们在每个之前使用异步。异步的目的是让所有可能的异步代码在继续之前完成

Jasmine 就是使用 it 全局函数来表示,和 describe 类似,字符串和方法两个参数。 

每个 Spec 内包括多个 expectation 来测试需要测试的代码,只要任何一个 expectation 结果为 false 就表示该测试用例为失败状态。

describe('demo test', () => {    const VALUE = true;    it('should be true', () => {        expect(VALUE).toBe(VALUE);    }) });

如果有很多需要测试的,可以多个it:

describe('AppComponent', () => {

  beforeEach(async(() => {

    TestBed.configureTestingModule({

      imports: [

        RouterTestingModule

      ],

      declarations: [

        AppComponent

      ],

    }).compileComponents();

  }));

  it('should create the app', async(() => {

    const fixture = TestBed.createComponent(AppComponent);

    const app = fixture.debugElement.componentInstance;

    expect(app).toBeTruthy();

  }));

});

断言,使用 expect 全局函数来表示,只接收一个代表要测试的实际值,并且需要与 Matcher 代表期望值


TestBed可以帮助我们创建app实例

代码中有3个it

第一个为异步测试app是否true或false 
如果app是0;两次取反当然是false; 
如果app是null;两次取反是false; 
如果app是undefined;两次取法是false; 
其余的,两次取反是true;

第二个为异步测试app是否有text属性,并且判断值是否和预期相同

第三个为异步测试app是否在h1标签中的显示值为预期值

测试Form

创建一个contact组件。

ng g c contact

首先我们修改contact.component HTML文件

<div>

    {{text}}

  </div>


  <form id="contact-form" [formGroup]="contactForm" (ngSubmit)="onSubmit()" novalidate>

    <div class="form-group">

      <label class="center-block">Name:

          <input class="form-control" formControlName="name">

      </label>

      <label class="center-block">Email:

        <input class="form-control" formControlName="email">

      </label>

      <label class="center-block">Text:

        <input class="form-control" formControlName="text">

      </label>

    </div>

    <button type="submit"

            [disabled]="!contactForm.valid" class="btn btn-success">Save</button>

  </form>

修改contact.component.ts文件

import { Component, OnInit } from '@angular/core';

import { FormGroup, FormControl, Validators } from '@angular/forms';


@Component({

  selector: 'app-contact',

  templateUrl: './contact.component.html',

  styleUrls: ['./contact.component.css']

})

export class ContactComponent {


  text = 'contact page';

  contactForm: FormGroup;

  contact = {

    name: '',

    email: '',

    text: ''

  };

  submitted = false;


  constructor() {

    this.createForm();

  }


  createForm(): void {

    this.contactForm = new FormGroup({

      'name': new FormControl(this.contact.name, [

        Validators.required,

        Validators.minLength(4)

      ]),

      'email': new FormControl(this.contact.email, [

        Validators.required,

        Validators.email

      ]),

      'text': new FormControl(this.contact.text, Validators.required)

    });

  }


  onSubmit(): void {

    this.submitted = true;

  }

}

修改app-routing.module.ts


import { ContactComponent } from './contact/contact.component';

import { NgModule } from '@angular/core';

import { Routes, RouterModule } from '@angular/router';


const routes: Routes = [

  {

    path: '',

    redirectTo: 'contact',

    pathMatch: 'full'

  },

  {

    path: 'contact',

    component: ContactComponent

  }

];


@NgModule({

  imports: [RouterModule.forRoot(routes)],

  exports: [RouterModule]

})

export class AppRoutingModule { }

此时终端执行:

npm start

image

修改测试文件contact.component.spec.ts

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

import { async, ComponentFixture, TestBed } from '@angular/core/testing';


import { ContactComponent } from './contact.component';

import { DebugElement } from '@angular/core';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';


describe('ContactComponent', () => {

  let comp: ContactComponent;

  let fixture: ComponentFixture<ContactComponent>;

  let de: DebugElement;

  let el: HTMLElement;


  beforeEach(async(() => {

    TestBed.configureTestingModule({

      declarations: [

        ContactComponent

      ],

      imports: [

        BrowserModule,

        FormsModule,

        ReactiveFormsModule

      ]

    })

    .compileComponents()

    .then(() => {

      fixture = TestBed.createComponent(ContactComponent);

      comp = fixture.componentInstance;

      de = fixture.debugElement.query(By.css('form'));

      el = de.nativeElement;

    });

  }));


  it(`should have as text 'contact page'`, async(() => {

    expect(comp.text).toEqual('contact page');

  }));


  it('should set submitted to true', async(() => {

    comp.onSubmit(); // 直接内部调用onSubmit函数, submitted被更改为true

    expect(comp.submitted).toBeTruthy();

  }));


  it('form call the onSubmit method', async(() => {

    fixture.detectChanges();

    spyOn(comp, 'onSubmit');

    el = fixture.debugElement.query(By.css('button')).nativeElement;

    el.click(); // 模拟在html界面上点击onSubmit,此时是不能被点击的,因为没有输入,所以次数应该是0

    expect(comp.onSubmit).toHaveBeenCalledTimes(0);

  }));


  it('form should be invalid', async(() => {

    comp.contactForm.controls['email'].setValue('');

    comp.contactForm.controls['name'].setValue('');

    comp.contactForm.controls['text'].setValue('');

    expect(comp.contactForm.valid).toBeFalsy();

  }));


  it('form should be vaild', async(() => {

    comp.contactForm.controls['email'].setValue('asd@asd.com');

    comp.contactForm.controls['name'].setValue('aada');

    comp.contactForm.controls['text'].setValue('text');

    expect(comp.contactForm.valid).toBeTruthy();

  }));

});

此时执行:

ng test

这里写图片描述

我们来分析一下,这个测试文件做了哪些东西?

导入依赖模块BrowserModule,FormsModule,ReactiveFormsModule

使用”By”将DOM中的form导入进来

第一个测试text属性

测试onSubmit函数调用

第三个测试使用“fixture”对象的函数“detectChanges”将组件状态应用于HTML,然后从DOM获取提交按钮并触发单击事件。在此之前,我们在组件的“onSubmit”功能上创建一个jasmine “spy”。最后,我们期望onSubmit函数不会被执行,因为这个按钮应该被禁用,因为表单无效。

第四个测试将无效值设置为组件表单,并期望表单有效属性为false。

最后,在第五个测试中,我们将有效值设置为表单并期望表单有效属性为真。


小提示 

- detectChanges 

在测试中的Angular变化检测。每个测试程序都通过调用fixture.detectChanges()来通知Angular执行变化检测。 

- By 

By类是Angular测试工具之一,它生成有用的predicate。 它的By.css静态方法产生标准CSS选择器 predicate,与JQuery选择器相同的方式过滤。


猜你喜欢

  • 编程语言排行

只有登录之后才可以评论,请点击这里进行登录