Angular 2
Тестирование приложения Angular 2
Поиск…
Установка схемы тестирования жасмина
Самый распространенный способ тестирования приложений Angular 2 - с помощью тестовой среды Jasmine. Жасмин позволяет вам проверять свой код в браузере.
устанавливать
Чтобы начать работу, вам нужен только пакет jasmine-core
(не jasmine
).
npm install jasmine-core --save-dev --save-exact
проверить
Чтобы проверить правильность настройки Жасмина, создайте файл ./src/unit-tests.html
со следующим содержимым и откройте его в браузере.
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>Ng App Unit Tests</title> <link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css"> <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script> <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script> <script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script> </head> <body> <!-- Unit Testing Chapter #1: Proof of life. --> <script> it('true is true', function () { expect(true).toEqual(true); }); </script> </body> </html>
Настройка тестирования с помощью Gulp, Webpack, Karma и Jasmine
Первое, что нам нужно - сказать карме, чтобы использовать Webpack для чтения наших тестов, в соответствии с конфигурацией, которую мы установили для механизма webpack. Здесь я использую babel, потому что я пишу свой код в ES6, вы можете изменить его для других вкусов, таких как TypScript. Или я использую шаблоны Pug (ранее Jade), вам это не нужно.
Тем не менее стратегия остается прежней.
Итак, это конфигурация webpack:
const webpack = require("webpack");
let packConfig = {
entry: {},
output: {},
plugins:[
new webpack.DefinePlugin({
ENVIRONMENT: JSON.stringify('test')
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude:/(node_modules|bower_components)/,
loader: "babel",
query:{
presets:["es2015", "angular2"]
}
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
loader: "file"
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
},
{
test: /\.pug$/,
loader: 'pug-html-loader'
},
]
},
devtool : 'inline-cheap-source-map'
};
module.exports = packConfig;
И тогда нам нужен файл karma.config.js для использования этой конфигурации webpack:
const packConfig = require("./webpack.config.js");
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
exclude:[],
files: [
{pattern: './karma.shim.js', watched: false}
],
preprocessors: {
"./karma.shim.js":["webpack"]
},
webpack: packConfig,
webpackServer: {noInfo: true},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['PhantomJS'],
concurrency: Infinity,
autoWatch: false,
singleRun: true
});
};
До сих пор мы сказали Карме использовать webpack, и мы сказали ему начать с файла karma.shim.js . этот файл будет работать как отправная точка для webpack. webpack прочитает этот файл и будет использовать инструкции import и require для сбора всех наших зависимостей и выполнения наших тестов.
Итак, давайте посмотрим на файл karma.shim.js:
// Start of ES6 Specific stuff
import "es6-shim";
import "es6-promise";
import "reflect-metadata";
// End of ES6 Specific stuff
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import "zone.js/dist/sync-test";
import "zone.js/dist/proxy-zone";
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
Error.stackTraceLimit = Infinity;
import {TestBed} from "@angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
let testContext = require.context('../src/app', true, /\.spec\.js/);
testContext.keys().forEach(testContext);
По сути, мы импортируем TestBed из тестирования угловых ядер и инициируем среду, так как ее нужно инициировать только один раз для всех наших тестов. Затем мы рекурсивно просматриваем каталог src / app и читаем каждый файл, который заканчивается на .spec.js, и передаем их в testContext, поэтому они будут запускаться.
Обычно я пытаюсь поставить свои тесты на то же место, что и класс. Чувствительный вкус, мне легче импортировать зависимости и рефакторинг с классами. Но если вы хотите поместить свои тесты в другое место, например, например, в каталог src / test , вот вам шанс. измените строку до последнего в файле karma.shim.js.
Отлично. Что слева? ah, задача gulp, которая использует файл karma.config.js, который мы сделали выше:
gulp.task("karmaTests",function(done){
var Server = require("karma").Server;
new Server({
configFile : "./karma.config.js",
singleRun: true,
autoWatch: false
}, function(result){
return result ? done(new Error(`Karma failed with error code ${result}`)):done();
}).start();
});
Теперь я запускаю сервер с созданным конфигурационным файлом, говоря ему, чтобы он запускался один раз и не следил за изменениями. Я считаю, что это лучше подходит для меня, поскольку тесты будут выполняться только в том случае, если я буду готов к их запуску, но, конечно, если вы захотите по-другому, вы знаете, где меняться.
И как мой последний пример кода, вот набор тестов для Углового 2 учебника «Тур героев».
import {
TestBed,
ComponentFixture,
async
} from "@angular/core/testing";
import {AppComponent} from "./app.component";
import {AppModule} from "./app.module";
import Hero from "./hero/hero";
describe("App Component", function () {
beforeEach(()=> {
TestBed.configureTestingModule({
imports: [AppModule]
});
this.fixture = TestBed.createComponent(AppComponent);
this.fixture.detectChanges();
});
it("Should have a title", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.title).toEqual("Tour of Heros");
});
}));
it("Should have a hero", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.selectedHero).toBeNull();
});
}));
it("Should have an array of heros", async(()=>
this.fixture.whenStable().then(()=> {
const cmp = this.fixture.componentInstance;
expect(cmp.heroes).toBeDefined("component should have a list of heroes");
expect(cmp.heroes.length).toEqual(10, "heroes list should have 10 members");
cmp.heroes.map((h, i)=> {
expect(h instanceof Hero).toBeTruthy(`member ${i} is not a Hero instance. ${h}`)
});
})));
it("Should have one list item per hero", async(()=>
this.fixture.whenStable().then(()=> {
const ul = this.fixture.nativeElement.querySelector("ul.heroes");
const li = Array.prototype.slice.call(
this.fixture.nativeElement.querySelectorAll("ul.heroes>li"));
const cmp = this.fixture.componentInstance;
expect(ul).toBeTruthy("There should be an unnumbered list for heroes");
expect(li.length).toEqual(cmp.heroes.length, "there should be one li for each hero");
li.forEach((li, i)=> {
expect(li.querySelector("span.badge"))
.toBeTruthy(`hero ${i} has to have a span for id`);
expect(li.querySelector("span.badge").textContent.trim())
.toEqual(cmp.heroes[i].id.toString(), `hero ${i} had wrong id displayed`);
expect(li.textContent)
.toMatch(cmp.heroes[i].name, `hero ${i} has wrong name displayed`);
});
})));
it("should have correct styling of hero items", async(()=>
this.fixture.whenStable().then(()=> {
const hero = this.fixture.nativeElement.querySelector("ul.heroes>li");
const win = hero.ownerDocument.defaultView ||hero.ownerDocument.parentWindow;
const styles = win.getComputedStyle(hero);
expect(styles["cursor"]).toEqual("pointer", "cursor should be pointer on hero");
expect(styles["borderRadius"]).toEqual("4px", "borderRadius should be 4px");
})));
it("should have a click handler for hero items",async(()=>
this.fixture.whenStable().then(()=>{
const cmp = this.fixture.componentInstance;
expect(cmp.onSelect)
.toBeDefined("should have a click handler for heros");
expect(this.fixture.nativeElement.querySelector("input.heroName"))
.toBeNull("should not show the hero details when no hero has been selected");
expect(this.fixture.nativeElement.querySelector("ul.heroes li.selected"))
.toBeNull("Should not have any selected heroes at start");
spyOn(cmp,"onSelect").and.callThrough();
this.fixture.nativeElement.querySelectorAll("ul.heroes li")[5].click();
expect(cmp.onSelect)
.toHaveBeenCalledWith(cmp.heroes[5]);
expect(cmp.selectedHero)
.toEqual(cmp.heroes[5], "click on hero should change hero");
})
));
});
Примечательно, что у нас есть beforeEach () настроить тестовый модуль и создать компонент в тесте, и как мы называем detectChanges (), так что угловой фактически проходит через двойное связывание и все.
Обратите внимание, что каждый тест является вызовом async (), и он всегда ждет, когда команда «Стабильность» решит проблему перед исследованием прибора. Затем он имеет доступ к компоненту через componentInstance и к элементу через nativeElement .
Существует один тест, который проверяет правильный стиль. как часть учебника, команда Angular демонстрирует использование стилей внутри компонентов. В нашем тесте мы используем getComputedStyle (), чтобы проверить, что стили идут от того, где мы указали, однако для этого нам нужен объект Window, и мы получаем его из элемента, как вы можете видеть в тесте.
Тестирование службы Http
Обычно службы вызывают удаленный Api для извлечения / отправки данных. Но модульные тесты не должны выполнять сетевые вызовы. Угловая внутренне использует класс XHRBackend
для выполнения HTTP-запросов. Пользователь может переопределить это, чтобы изменить поведение. Модуль углового тестирования предоставляет MockBackend
и MockConnection
которые могут использоваться для тестирования и утверждения HTTP-запросов.
posts.service.ts
Эта служба попадает в конечную точку api для получения списка сообщений.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/rx'; import 'rxjs/add/operator/map'; export interface IPost { userId: number; id: number; title: string; body: string; } @Injectable() export class PostsService { posts: IPost[]; private postsUri = 'http://jsonplaceholder.typicode.com/posts'; constructor(private http: Http) { } get(): Observable<IPost[]> { return this.http.get(this.postsUri) .map((response) => response.json()); } }
posts.service.spec.ts
Здесь мы проверим выше сервис, насмехаясь над вызовами http api.
import { TestBed, inject, fakeAsync } from '@angular/core/testing'; import { HttpModule, XHRBackend, ResponseOptions, Response, RequestMethod } from '@angular/http'; import { MockBackend, MockConnection } from '@angular/http/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { // Mock http response const mockResponse = [ { 'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto' }, { 'userId': 1, 'id': 2, 'title': 'qui est esse', 'body': 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla' }, { 'userId': 1, 'id': 3, 'title': 'ea molestias quasi exercitationem repellat qui ipsa sit aut', 'body': 'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut' }, { 'userId': 1, 'id': 4, 'title': 'eum et est occaecati', 'body': 'ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit' } ]; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpModule], providers: [ { provide: XHRBackend, // This provides mocked XHR backend useClass: MockBackend }, PostsService ] }); }); it('should return posts retrieved from Api', fakeAsync( inject([XHRBackend, PostsService], (mockBackend, postsService) => { mockBackend.connections.subscribe( (connection: MockConnection) => { // Assert that service has requested correct url with expected method expect(connection.request.method).toBe(RequestMethod.Get); expect(connection.request.url).toBe('http://jsonplaceholder.typicode.com/posts'); // Send mock response connection.mockRespond(new Response(new ResponseOptions({ body: mockResponse }))); }); postsService.get() .subscribe((posts) => { expect(posts).toBe(mockResponse); }); }))); });
Тестирование угловых компонентов - базовые
Код компонента приведен ниже.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
Для угловых испытаний угловые обеспечивают свои тестовые утилиты вместе с испытательной основой, которая помогает при написании хорошего тестового случая в угловом режиме. Угловые утилиты могут быть импортированы из @angular/core/testing
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyAppComponent } from './banner-inline.component';
describe('Tests for MyAppComponent', () => {
let fixture: ComponentFixture<MyAppComponent>;
let comp: MyAppComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MyAppComponent
]
});
});
beforeEach(() => {
fixture = TestBed.createComponent(MyAppComponent);
comp = fixture.componentInstance;
});
it('should create the MyAppComponent', () => {
expect(comp).toBeTruthy();
});
});
В приведенном выше примере существует только один тестовый пример, который объясняет тестовый пример существования компонента. В приведенном выше примере используются утилиты углового тестирования, такие как TestBed
и ComponentFixture
.
TestBed
используется для создания модуля углового тестирования, и мы настраиваем этот модуль с configureTestingModule
метода configureTestingModule
для создания среды модуля для класса, который мы хотим протестировать. Модуль тестирования должен быть настроен перед выполнением каждого тестового примера, поэтому мы настраиваем модуль тестирования в функции beforeEach
.
createComponent
метод TestBed
используется для создания экземпляра компонента в процессе тестирования. createComponent
возвращает ComponentFixture
. Устройство обеспечивает доступ к самому экземпляру компонента.