Angularjs   发布时间:2022-04-20  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了Angular单元测试与E2E测试大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
本文介绍了Angular单元测试和E2E测试的配置与测试方法。示例APP使用Angular 7 CLI创建,已配置好基础测试环境,生成了测试样例代码认,Angular单元测试使用Jasmine测试框架和Karma测试运行器,E2E测试使用Jasmine测试框架和Protractor端到端测试框架。

配置单元测试

Jasmine一个用于测试JavaScript的行为驱动开发框架,不依赖于任何其他JavaScript框架。
Karma是测试运行器,为开发人员提供了高效、真实的测试环境,支持多种浏览器,易于调试。

配置文件

单元测试配置文件test.ts和karma.conf.js:
test.ts

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.ts。
karma.conf.js

@H_488_17@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 }); };

认使用Chrome浏览器,可生成单元测试报告和覆盖率报告,覆盖率报告保存在根目录coverage文件夹内,启用autoWatch。
singleRun认为false,如设为true则测试结束后会自动退出并根据测试结果返回代码0或1,常用于CI环境。

浏览器配置

Karma支持的浏览器:

  • Chrome
  • ChromeCanary
  • ChromeHeadless
  • PhantomJS
  • Firefox
  • Opera
  • IE
  • Safari

可同时配置多个浏览器进行测试,要启用其他浏览器,需安装依赖,比如启用Firefox:

npm install karma-firefox-launcher --save-dev

然后在karma.conf.js内增加配置:

...
require(‘karma-chrome-launcher‘),require(‘karma-firefox-launcher‘),...
browsers: [‘Chrome‘,‘Firefox‘],...

运行测试

用CLI创建App生成一个单元测试文件app.component.spec.ts。执行CLI命令ng test即可运行单元测试:

ng test

运行后在控制台输出测试结果并打开浏览器:

Angular单元测试与E2E测试


浏览器会显示测试结果,总测试数,失败数。在顶部,每个点或叉对应一个测试用例,点表示成功,叉表示失败,鼠标移到点或叉上会显示测试信息。点击测试结果中的某一行,可重新运行某个或某组(测试套件)测试。

常用参数:
--browsers 指定使用的浏览器
--code-coverage 输出覆盖率报告
--code-coverage-exclude 排除文件或路径
--karma-config 指定Karma配置文件
--prod 启用production环境
--progress 认为true,将编译进度输出到控制台
--watch 认为true,代码修改后会重新运行测试

自定义Launcher

karma-chrome-launcher、karma-firefox-launcher、karma-ie-launcher等均支持自定义Launcher,customLaunchers与--browsers结合使用可满足多种环境的测试需求。每种浏览器支持自定义属性请查看Karma Browsers文档。
比如,CI环境下常用Headless模式,不必使用浏览器界面,在karma.conf.js中增加如下配置:

browsers: [‘Chrome‘],customLaunchers: {
  ChromeHeadlessCI: {
    base: ‘ChromeHeadless‘,flags: [‘--no-sandBox‘]
  }
},

运行如下命令进行测试:

ng test --watch=false --progress=false --browsers=ChromeHeadlessCI

测试覆盖率

运行如下命令生成测试覆盖率报告,报告保存在项目根目录下的coverage文件夹内:

ng test --watch=false --code-coverage

如想每次测试都生成报告,可修改CLI配置文件angular.json:

"test": {
  "options": {
    "codeCoverage": true
  }
}

设置排除的文件或路径

ng test --watch=false --code-coverage --code-coverage-exclude=src/app/heroes/heroes.component.ts --code-coverage-exclude=src/app/hero-search/*

同样可以在angular.json中配置:

"test": {
  "options": {
    "codeCoverage": true,"codeCoverageExclude": ["src/app/heroes/heroes.component.ts","src/app/hero-search/*"]
  }
}

设定测试覆盖率指标
编辑配置文件karma.conf.js,增加如下内容

coverageIstanbulReporter: {
  reports: [ ‘html‘,‘lcovonly‘ ],fixWebpacksourcePaths: true,thresholds: {
    statements: 80,lines: 80,branches: 80,functions: 80
  }
}

测试报告中达到标准的背景为绿色:

Angular单元测试与E2E测试


注意:与CI集成时不要设置覆盖率指标,否则若未到达指标,Job会终止。
LCOV
coverageIstanbulReporter中reports参数为[ ‘html‘,‘lcovonly‘ ],会生成html和lcov两种格式的报告。报告文件lcov.info可与Sonar集成,在Sonar管理界面配置LCOV Files路径,即可在Sonar中查看测试情况。

Angular单元测试与E2E测试

编写测试

一个测试

使用CLI创建service、Component等时会自动创建测试文件,我们以创建App时生成的测试文件app.component.spec.ts为例:

import {async,TESTBeD} 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‘,() => {
    const fixture = TESTBed.createComponent(AppComponent);
    const app = fixture.debugElement.componenTinstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title ‘Hello‘`,() => {
    const fixture = TESTBed.createComponent(AppComponent);
    const app = fixture.debugElement.componenTinstance;
    expect(app.titlE).toEqual(‘Hello‘);
  });

  it(‘should render title in a h1 tag‘,() => {
    const fixture = TESTBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySELEctor(‘h1‘).textContent).toContain(‘Welcome to Hello!‘);
  });
});

测试结构
从上例我们可以了解测试的主要结构:
describe函数中包含了beforeEach和it两类函数。describe相当于Java测试中的suite,也就是测试组,其中可以包含多个测试用例it。一般一个测试文件含有一个describe,当然也可以有多个。beforeEach相当于Java测试中的@Before方法,每个测试用例执行前调用一次。同样,还有afterEach、beforeAll、afterAll函数,afterEach在每个测试用例执行后调用一次,beforeAll、afterAll相当于Java测试中的@BeforeClass、@AfterClass方法,每个describe执行前后调用一次。

describe和it的第一个参数是测试的说明。it中可以包含一个或多个expect来执行测试验证。

TESTBed
TESTBed是Angular测试中最重要的工具。

TESTBed.configureTesTingModule()方法动态构建TesTingModule来模拟Angular @NgModule, 支持@NgModule的大多数属性

在测试中需导入必要的依赖:要测试的组件及依赖。在AppComponent页面中使用了router-outlet,因此我们导入了RouterTesTingModule来模拟RouterModule。Test Module预配置了一些元素,比如BrowserModule,不需导入。

TESTBed.createComponent()方法创建组件实例,返回ComponentFixture。ComponentFixture是一个测试工具(test harness),用于与创建的组件及相应的元素进行交互。

nativeElement和DebugElement
在示例中使用了fixture.debugElement.nativeElement,也可以写成fixture.nativeElement。实际上,fixture.nativeElement是fixture.debugElement.nativeElement的一种简化写法。nativeElement依赖于运行时环境,Angular依赖DebugElement抽象来支持跨平台。Angular创建DebugElement tree来包装native element,nativeElement返回平台相关的元素对象。

我们的测试样例仅运行在浏览器中,因此nativeElement总为HTMLElement,可以使用querySELEctor()、querySELEctorAll()方法查询元素。

element.querySELEctor(‘p‘);
element.querySELEctor(‘input‘);
element.querySELEctor(‘.welcome‘);
element.querySELEctorAll(‘span‘);

detectChanges
createComponent() 函数不会绑定数据,必须调用fixture.detectChanges()来执行数据绑定,才能在组件元素中取得内容

it(‘should render title in a h1 tag‘,() => {
  const fixture = TESTBed.createComponent(AppComponent);
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySELEctor(‘h1‘).textContent).toContain(‘Welcome to Hello!‘);
});

当数据模型值改变后,也需调用fixture.detectChanges()方法

it(‘should render title in a h1 tag‘,() => {
  const fixture = TESTBed.createComponent(AppComponent);
  const app = fixture.componenTinstance;
  app.title = ‘china‘;
  fixture.detectChanges();
  const compiled = fixture.nativeElement;
  expect(compiled.querySELEctor(‘h1‘).textContent).toContain(‘Welcome to china!‘);
});

可以配置自动检测,增加ComponentFixtureAutoDetect provider:

import { ComponentFixtureAutoDetect } from ‘@angular/core/tesTing‘;
...
TESTBed.configureTesTingModule({
  providers: [
    { provide: ComponentFixtureAutoDetect,useValue: true }
  ]
});

启用自动检测后仅需在数值改变后调用detectChanges():

it(‘should display original title‘,() => {
  // Hooray! No `fixture.detectChanges()` needed
  expect(h1.textContent).toContain(comp.titlE);
});

it(‘should still see original title after comp.title change‘,() => {
  const oldtitle = comp.title;
  comp.title = ‘Test title‘;
  // Displayed title is old because Angular didn‘t hear the change :(
  expect(h1.textContent).toContain(oldtitlE);
});

it(‘should display updated title after detectChanges‘,() => {
  comp.title = ‘Test title‘;
  fixture.detectChanges(); // detect changes explicitly
  expect(h1.textContent).toContain(comp.titlE);
});

同步和异步beforeEach
组件常用 @Component.templateUrl 和 @Component.styleUrls 属性来指定外部模板和CSS,Angular编译器会在编译期间读取外部文件

@Component({
  SELEctor: ‘app-bAnner‘,templateUrl: ‘./bAnner-external.component.html‘,styleUrls:  [‘./bAnner-external.component.css‘]
})
beforeEach(() => {
  TESTBed.configureTesTingModule({
    declarations: [ BAnnerComponent ],});
  fixture = TESTBed.createComponent(BAnnerComponent);
});

当用CLI 的ng test命令运行含有如上同步beforeEach方法的测试时没有问题,因为会在运行测试之前先编译。若在非 CLI 环境下运行这些测试则可能失败。要解决这个问题,可以调用compileComponents()进行显示的编译。compileComponents()方法是异步的,必须在async()方法调用

beforeEach(async(() => {
  TESTBed.configureTesTingModule({
    imports: [
      RouterTesTingModule
    ],declarations: [
      AppComponent
    ],}).compileComponents();
}));

调用 compileComponents() 会关闭当前的 TESTBed 实例,不再允许进行配置,不能再调用任何 TESTBed 中的配置方法,既不能调 configureTesTingModule(),也不能调用任何 override... 方法

常同时使用同步beforeEach和异步beforeEach来协同工作,异步的 beforeEach() 负责编译组件,同步的 beforeEach() 负责执行其余的准备代码。测试运行器会先调用异步 beforeEach方法,运行完毕后再调用同步方法

重构
示例中重复代码较多,我们用两个beforeEach来简化一下:

import {async,ComponentFixture,() => {
  let fixture: ComponentFixture<AppComponent>;
  let app: AppComponent;

  beforeEach(async(() => {
    TESTBed.configureTesTingModule({
      imports: [
        RouterTesTingModule
      ],}).compileComponents();
  }));

  beforeEach(() => {
    fixture = TESTBed.createComponent(AppComponent);
    app = fixture.componenTinstance;
    fixture.detectChanges();
  });

  it(‘should create the app‘,() => {
    expect(app).toBeTruthy();
  });

  it(`should have as title ‘Hello‘`,() => {
    expect(app.titlE).toEqual(‘Hello‘);
  });

  it(‘should render title in a h1 tag‘,() => {
    const compiled = fixture.nativeElement;
    expect(compiled.querySELEctor(‘h1‘).textContent).toContain(‘Welcome to Hello!‘);
  });
});

也可以把这两个 beforeEach() 重整成一个异步的 beforeEach():

beforeEach(async(() => {
  TESTBed.configureTesTingModule({
     imports: [
        RouterTesTingModule
      ],})
  .compileComponents()
  .then(() => {
    fixture = TESTBed.createComponent(AppComponent);
    app = fixture.componenTinstance;
    fixture.detectChanges();
  });
}));

依赖注入与mock

对简单对象进行测试可以用new创建实例:

describe(‘Valueservice‘,() => {
  let service: Valueservice;
  beforeEach(() => { service = new Valueservice(); });
    ...
});

不过大多数service、Component等有多个依赖项,使用New很不方便。若用DI来创建测试对象,当依赖其他服务时,DI会找到或创建依赖的服务。要测试某个对象,在configureTesTingModule中配置测试对象本身及依赖项,然后调用TESTBed.get()注入测试对象:

beforeEach(() => {
  TESTBed.configureTesTingModule({ providers: [Valueservice] });
  service = TESTBed.get(ValueservicE);
});

单元测试的原则之一:仅对要测试对象本身进行测试,而不对其依赖项进行测试,依赖项通过mock方式注入,而不使用实际的对象,否则测试不可控

mock优先使用Spy方式:

let masterservice: Masterservice;

beforeEach(() => {
  const spy = jasmine.createSpyObj(‘Valueservice‘,[‘getValue‘]);
    spy.getValue.and.returnValue(‘stub value‘);

  TESTBed.configureTesTingModule({
    // Provide both the service-to-test and its (spy) dependency
    providers: [
      Masterservice,{ provide: Valueservice,useValue: spy }
    ]
  });

  masterservice = TESTBed.get(MasterservicE);
});

httpClient、Router、LOCATIOn

同测试含其它依赖的对象一样可以使用spy方式:

beforeEach(() => {
  const httpClientSpy = jasmine.createSpyObj(‘httpClient‘,[‘get‘]);

  TESTBed.configureTesTingModule({
    providers: [
      {provide: httpClient,useValue: httpClientSpy}
    ]
  });
});
beforeEach(async(() => {
  const routerSpy = jasmine.createSpyObj(‘Router‘,[‘navigateByUrl‘]);
  const LOCATIOnSpy = jasmine.createSpyObj(‘LOCATIOn‘,[‘BACk‘]);

  TESTBed.configureTesTingModule({
    providers: [
      {provide: Router,useValue: routerSpy},{provide: LOCATIOn,useValue: LOCATIOnSpy}
    ]
  })
    .compileComponents();
}));

Component测试

  • 仅测试组件类

测试组件类就像测试服务那样简单:
组件类

export class WelcomeComponent  implements OnInit {
  welcome: String;
  constructor(private userservice: UserservicE) { }

  ngOnInit(): void {
    this.welcome = thiS.Userservice.isLoggedIn ?
      ‘Welcome,‘ + thiS.Userservice.user.name : ‘Please log in.‘;
  }
}

mock

class mockUserservice {
  isLoggedIn = true;
  user = { name: ‘Test User‘};
};

测试

...
beforeEach(() => {
  TESTBed.configureTesTingModule({
    // provide the component-under-test and dependent service
    providers: [
      WelcomeComponent,{ provide: Userservice,useClass: mockUserservice }
    ]
  });
  // inject both the component and the dependent service.
  comp = TESTBed.get(WelcomeComponent);
  userservice = TESTBed.get(UserservicE);
});
...
it(‘should ask user to log in if not logged in after ngOnInit‘,() => {
  userservice.isLoggedIn = false;
  comp.ngOnInit();
  expect(comp.welcomE).not.toContain(userservice.user.Name);
  expect(comp.welcomE).toContain(‘log in‘);
});
  • 组件DOM测试

只涉及类的测试可以判断组件类的行为是否正常,但不能确定组件是否能正常渲染和交互。
进行组件DOM测试,需要使用TESTBed.createComponent()等方法,第一个测试即为组件DOM测试。

TESTBed.configureTesTingModule({
  declarations: [ BAnnerComponent ]
});
const fixture = TESTBed.createComponent(BAnnerComponent);
const component = fixture.componenTinstance;
expect(component).toBeDefined();

dispatchEvent
为模拟用户输入,比如为input元素输入值,要找到input元素并设置它的 value 属性。Angular不知道你设置了input元素的value属性,需要调用 dispatchEvent() 触发输入框的 input 事件,再调用 detectChanges():

it(‘should convert hero name to title Case‘,() => {
  // get the name‘s input and display elements from the DOM
  const hostElement = fixture.nativeElement;
  const nameInput: HTMLInputElement = hostElement.querySELEctor(‘input‘);
  const nameDisplay: HTMLElement = hostElement.querySELEctor(‘span‘);

  nameInput.value = ‘quick BROWN  fOx‘;

  // dispatch a DOM event so that Angular learns of input value change.
  nameInput.dispatchEvent(newEvent(‘input‘));

  fixture.detectChanges();

  expect(nameDisplay.textContent).toBe(‘Quick Brown  Fox‘);
});

嵌套组件

组件中常常使用其他组件:

<app-bAnner></app-bAnner>
<app-welcome></app-welcome>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
  <a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>

对于无害的内嵌组件可以直接将其添加到declarations中,这是最简单的方式:

describe(‘AppComponent & TestModule‘,() => {
  beforeEach(async(() => {
    TESTBed.configureTesTingModule({
      declarations: [
        AppComponent,BAnnerComponent,WelcomeComponent
      ]
    })
    .compileComponents().then(() => {
      fixture = TESTBed.createComponent(AppComponent);
      comp    = fixture.componenTinstance;
    });
  }));
  ...
});

也可为无关紧要的组件创建一些测试桩:

@Component({SELEctor: ‘app-bAnner‘,template: ‘‘})
class BAnnerstubComponent {}

@Component({SELEctor: ‘router-outlet‘,template: ‘‘})
class RouterOutletstubComponent { }

@Component({SELEctor: ‘app-welcome‘,template: ‘‘})
class WelcomestubComponent {}

然后在TESTBed的配置中声明它们:

TESTBed.configureTesTingModule({
  declarations: [
    AppComponent,BAnnerstubComponent,RouterOutletstubComponent,WelcomestubComponent
  ]
})

另一种办法是使用NO_ERRORS_scheR_433_11845@A,要求 Angular编译器忽略那些不认识的元素和属性

TESTBed.configureTesTingModule({
  declarations: [
    AppComponent,RouterLinkDirectivestub
  ],scheR_433_11845@as: [ NO_ERRORS_scheR_433_11845@A ]
})

NO_ERRORS_scheR_433_11845@A方法比较简单,但不要过度使用。NO_ERRORS_scheR_433_11845@A 会阻止编译器因疏忽或拼写错误而缺失的组件和属性,如人工找出这些 bug会很费时。

属性指令测试

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

@Directive({ SELEctor: ‘[highlight]‘ })
/** Set BACkgroundColor for the attached element to highlight color and set the element‘s customProperty to true */
export class HighlightDirective implements OnChanges {

  defaultColor =  ‘rgb(211,211,211)‘; // lightgray

  @Input(‘highlight‘) bgColor: String;

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

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

属性型指令肯定要操纵 DOM,如只针对类测试不能证明指令的有效性。若通过组件来测试,单一的用例一般无法探索指令的全部能力。因此,更好的方法是创建一个能展示该指令所有用法的人造测试组件:

@Component({
  template: `
  <h2 highlight="yellow">Something Yellow</h2>
  <h2 highlight>The Default (Gray)</h2>
  <h2>No Highlight</h2>
  <input #Box [highlight]="Box.value" value="cyan"/>`
})
class TESTComponent { }

测试程序:

beforeEach(() => {
  fixture = TESTBed.configureTesTingModule({
    declarations: [ HighlightDirective,TESTComponent ]
  })
  .createComponent(TESTComponent);

  fixture.detectChanges(); // initial binding

  // all elements with an attached HighlightDirective
  des = fixture.debugElement.queryAll(By.directive(HighlightDirectivE));

  // the h2 without the HighlightDirective
  bareH2 = fixture.debugElement.query(By.css(‘h2:not([highlight])‘));
});

// color tests
it(‘should have three highlighted elements‘,() => {
  expect(des.length).toBe(3);
});

it(‘should color 1st <h2> BACkground "yellow"‘,() => {
  const bgColor = des[0].nativeElement.style.BACkgroundColor;
  expect(bgColor).toBe(‘yellow‘);
});

it(‘should color 2nd <h2> BACkground w/ default color‘,() => {
  const dir = des[1].injector.get(HighlightDirectivE) as HighlightDirective;
  const bgColor = des[1].nativeElement.style.BACkgroundColor;
  expect(bgColor).toBe(dir.defaultColor);
});

it(‘should bind <input> BACkground to value color‘,() => {
  // easier to work with nativeElement
  const input = des[2].nativeElement as HTMLInputElement;
  expect(input.style.BACkgroundColor).toBe(‘cyan‘,‘initial BACkgroundColor‘);

  // dispatch a DOM event so that Angular responds to the input value change.
  input.value = ‘green‘;
  input.dispatchEvent(newEvent(‘input‘));
  fixture.detectChanges();

  expect(input.style.BACkgroundColor).toBe(‘green‘,‘changed BACkgroundColor‘);
});

it(‘bare <h2> should not have a customProperty‘,() => {
  expect(bareH2.properties[‘customProperty‘]).toBeUndefined();
});

Pipe测试

describe(‘titleCasePipe‘,() => {
  // This pipe is a pure,stateless function so no need for BeforeEach
  let pipe = new titleCasePipe();

  it(‘transforms "abc" to "Abc"‘,() => {
    expect(pipe.transform(‘abc‘)).toBe(‘Abc‘);
  });

  it(‘transforms "abc def" to "Abc Def"‘,() => {
    expect(pipe.transform(‘abc def‘)).toBe(‘Abc Def‘);
  });

  ...
});

TesTing Module

RouterTesTingModule
在前面的测试中我们使用了测试桩RouterOutletstubComponent,与Router有关的测试还可以使用RouterTesTingModule:

beforeEach(async(() => {
  TESTBed.configureTesTingModule({
    imports: [
      RouterTesTingModule
    ],}).compileComponents();
}));

RouterTesTingModule还可以模拟路由:

beforeEach(() => {
  TESTBed.configureTestModule({
    imports: [
      RouterTesTingModule.withRoutes(
        [{path: ‘‘,component: BlankCmp},{path: ‘simple‘,component: SimpleCmp}]
      )
    ]
  });
});

httpClientTesTingModule

describe(‘httpClient tesTing‘,() => {
  let httpClient: httpClient;
  let httpTesTingController: httpTesTingController;

  beforeEach(() => {
    TESTBed.configureTesTingModule({
      imports: [ httpClientTesTingModule ]
    });

    // Inject the http service and test controller for each test
    httpClient = TESTBed.get(httpClient);
    httpTesTingController = TESTBed.get(httpTesTingController);
  });

  afterEach(() => {
    // After every test,assert that there are no more pending requests.
    httpTesTingController.verify();
  });

  it(‘can test httpClient.get‘,() => {
    const testData: Data = {name: ‘Test Data‘};

    // Make an http GET request
    httpClient.get<Data>(testurl)
      .subscribe(data =>
        // When observable resolves,result should match test data
        expect(data).toEqual(testData)
      );

    // The following `expectOne()` will match the request‘s URl.
    // If no requests or multiple requests matched that URL
    // `expectOne()` would throw.
    const req = httpTesTingController.expectOne(‘/data‘);

    // Assert that the request is a GET.
    expect(req.request.method).toEqual(‘GET‘);

    // Respond with mock data,causing Observable to resolve.
    // Subscribe callBACk asserts that correct data was returned.
    req.flush(testData);

    // Finally,assert that there are no outstanding requests.
    httpTesTingController.verify();
  });

    ...
});

调试

在测试结果浏览器中,点击“DEBUG”按钮会打开新浏标签页并重新运行测试程序。按"F12"打开调试界面,然后进入sources找到测试文件(CTRL+P),在测试程序中设置断点即可调试。

配置E2E测试

E2E测试使用Jasmine和Protractor测试框架,Protractor是Angular端到端测试框架。

安装Protractor

npm install -g protractor

项目中执行npm install时会安装protractor,不必单独执行以上命令。安装protractor后会安装两个命令行工具protractor和webdriver-manager(位于node_modules\protractor\bin目录),webdriver-manager负责管理驱动、启停SELEnium Server。

webdriver-manager命令:

clean      removes all downloaded driver files from the out_dir
start      start up the SELEnium server
shutdown   shut down the SELEnium server
status     list the current available drivers
update     install or update SELEcted binaries,更新的驱动保存在Node_modules\protractor\node_modules\webdriver-manager\SELEnium目录下
version    get the current version

配置文件

使用CLI创建的App会生成一个e2e项目,其中包含测试配置protractor.conf.js及测试代码
protractor.conf.js

const { SpecReporter } = require(‘jasmine-spec-reporter‘);

exports.config = {
  allScriptsTimeout: 11000,specs: [
    ‘./src/**/*.e2e-spec.ts‘
  ],capabilities: {
    ‘browserName‘: ‘chrome‘
  },directConnect: true,baseUrl: ‘http://localhost:4200/‘,framework: ‘jasmine‘,jasmineNodeOpts: {
    showColors: true,defaultTimeoutInterval: 30000,print: function() {}
  },onPrepare() {
    require(‘ts-node‘).register({
      project: require(‘path‘).join(__dirname,‘./tsconfig.e2e.json‘)
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  }
};

认,Protractor使用Jasmine测试框架,使用直连方式连接Chrome浏览器,测试文件扩展名为.e2e-spec.ts。

浏览器配置

Protractor支持Chrome、Firefox、Safari、IE等浏览器。
多浏览器
Protractor可同时启动多个浏览器,用一个浏览器时,在配置中使用capabilities选项;用多个浏览器时,使用multiCapabilities:

@H_488_17@multiCapabilities: [{ browserName: ‘firefox‘ },{ browserName: ‘chrome‘ }]

另外需在package.json中增加配置:

"scripts": {
  "webdriver-update": "webdriver-manager update"
}

在运行测试前执行:

npm run webdriver-update

否则项目中的驱动不会更新(认只有chrome驱动,在命令行运行webdriver-manager update仅更新全局的驱动),运行测试会报如下错误

No update-config.json found. Run ‘webdriver-manager update‘ to download binaries

浏览器选项

capabilities: {
  ‘browserName‘: ‘chrome‘,‘chromeOptions‘: {
    ‘args‘: [‘show-fps-counter=true‘]
  }
},
capabilities: {
  ‘browserName‘: ‘firefox‘,‘moz:firefoxOptions‘: {
    ‘args‘: [‘--safe-mode‘]
  }
},

更多选项请查看相应驱动ChromeDriverGeckoDriver

SELEnium Server配置

使用Standalone SELEnium Server时,需安装JDK。
更新driver后启动SELEnium Server:

webdriver-manager update
webdriver-manager start

删除原配置中的directConnect、baseUrl:

directConnect: true,

增加SELEniumAddress(认为http://localhost:4444/wd/hub):

SELEniumAddress: ‘http://localhost:4444/wd/hub‘,

运行测试

执行CLI命令 ng e2e即可运行E2E测试:

ng e2e

常用参数:

--base-url  Base URL for protractor to connect to.
--configuration (-C)  A named configuration environment,as specified in the "configurations" section of angular.json.
--host  Host to listen on.
--port  The port to use to serve the application.
--prod  When true,sets the build configuration to the production environment.
--protractor-config  The name of the Protractor configuration file.
--webdriver-update  Try to update webdriver.

指定配置文件

不同的环境若配置不同,可使用不同的配置文件

比如,在CI环境中启用Chrome Headless模式:
在e2e根目录下创建一名为protractor-ci.conf.js的新文件内容如下:

const config = require(‘./protractor.conf‘).config;

config.capabilities = {
  browserName: ‘chrome‘,chromeOptions: {
    args: [‘--headless‘,‘--no-sandBox‘]
  }
};

exports.config = config;

注意: windows系统要增加参数--disable-gpu

运行以下命令测试:

ng e2e --protractor-config=e2e\protractor-ci.conf.js

编写E2E测试

一个测试

import { AppPage } from ‘./app.po‘;

describe(‘workspace-project App‘,() => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it(‘should display welcome message‘,() => {
    page.navigateTo();
    expect(page.gettitleText()).toEqual(‘Welcome to Hello!‘);
  });
});
import { browser,by,element } from ‘protractor‘;

export class AppPage {
  navigateTo() {
    return browser.get(‘/‘);
  }

  gettitleText() {
    return element(by.css(‘app-root h1‘)).getText();
  }
}

E2E测试与单元测试都使用了Jasmine,测试结构相同。Protractor提供了全局的browser、element、by,分别用来打开页面和查找元素。

Protractor

describe(‘Protractor Demo App‘,function() {
  it(‘should add one and two‘,function() {
    browser.get(‘http://juliemr.github.io/protractor-demo/‘);
    element(by.model(‘first‘)).sendKeys(1);
    element(by.model(‘second‘)).sendKeys(2);

    element(by.id(‘gobutton‘)).click();

    expect(element(by.binding(‘latest‘)).getText()).
        toEqual(‘5‘); // This is wrong!
  });
});

大佬总结

以上是大佬教程为你收集整理的Angular单元测试与E2E测试全部内容,希望文章能够帮你解决Angular单元测试与E2E测试所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。