让我们来看看由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,但你可以安装和使用其他浏览器启动器。
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的常用参数.
- –code-coverage -cc 代码覆盖率报告, 默认这个是不开启的, 因为生成报告的速度还是比较慢的.
- –colors 输出结果使用各种颜色 默认开启
- –single-run -sr 执行测试, 但是不检测文件变化 默认不开启
- –progress 把测试的过程输出到控制台 默认开启
- –sourcemaps -sm 生成sourcemaps 默认开启
- –watch -w 运行测试一次, 并且检测变化 默认开启
在弹出的chrome浏览器窗口中显示:
我们详细介绍一下这个测试代码
导入测试文件的所有依赖项
这里要注意,你在组件内使用的依赖,这里面同样需要导入,否则会无法运行。
使用describe
开始我们的测试
describe是一个函数,Jasmine 就是使用 describe 全局函数来测试的。
declare function describe(description: string, specDefinitions: () => void): void;
表示分组类似测试套,也就是一组测试用例,支持description
嵌套。
例子:
我们在每个之前使用异步。异步的目的是让所有可能的异步代码在继续之前完成
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
标签中的显示值为预期值
创建一个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 { }
此时终端执行:
修改测试文件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();
}));
});
此时执行:
我们来分析一下,这个测试文件做了哪些东西?
导入依赖模块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选择器相同的方式过滤。
只有登录之后才可以评论,请点击这里进行登录