サーチ…


ジャスミンテストフレームワークのインストール

Angular 2アプリをテストする最も一般的な方法は、Jasmineテストフレームワークです。 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を使用してテストを読むように指示することです。ここでは、私はES6で自分のコードを書くので、私はバベルを使用しています、あなたはTypescriptのような他の味のためにそれを変更することができます。または私は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;

次に、webpack configを使用するには、karma.config.jsファイルが必要です。

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

これまで、Karmaにwebpackを使用するように指示し、 karma.shim.jsというファイルから開始するように指示しました 。このファイルはwebpackの開始点として機能する仕事をします。 webpackはこのファイルを読み込み、 importrequireステートメントを使用して依存関係をすべて収集し、テストを実行します。

今度は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に渡して実行します。

私は通常、私のテストをクラスと同じ場所に置こうとします。 Personatの味は、クラスで依存関係やリファクタのテストをインポートするのが簡単になります。しかし、 src / testディレクトリのようにテストをどこかに置いておきたい場合は、ここにあなたのチャンスがあります。 karma.shim.jsファイルの最後の行を変更してください。

完璧。何が残っていますか? ah、上記で作成したkarma.config.jsファイルを使用するgulpタスク:

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();
}); 

私は今作成した設定ファイルでサーバを起動しています。一度実行するように指示し、変更を監視しません。私は、私が彼らが走る準備ができている場合にのみテストが実行されるので、私はこのスイートを私によく見いだしますが、あなたが異なってほしい場合は、どこを変更するかを知っています。

そして最後のコードサンプルとして、Angular 2チュートリアル「Tour of Heroes」のテストセットがあります。

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()がテストモジュールを構成し、テストでコンポーネントを作成する方法と、angularが実際にダブルバインディングとallを通過するようにdetectChanges()を呼び出す方法です。

各テストはasync()への呼び出しであり、fixtureを調べる前に常にwhenStableが解決することを約束します。次に、componentInstanceを介してコンポーネントにアクセスし、 nativeElementを通じて要素にアクセスします。

正しいスタイリングをチェックしているテストが1つあります。チュートリアルの一環として、Angularチームはコンポーネント内でスタイルを使用する方法を示します。テストでは、 getComputedStyle()を使ってスタイルが指定された場所から来ていることを確認しますが、そのためにWindowオブジェクトが必要です。テストで確認できるように、要素から取得しています。

HTTPサービスのテスト

通常、サービスはリモートApiを呼び出してデータを取得/送信します。しかし、単体テストはネットワークコールを行うべきではありません。 Angularは内部的にXHRBackendクラスを使用してHTTP要求を行います。ユーザーはこれを変更して動作を変更できます。角度試験モジュールは提供MockBackendMockConnectionテストし、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';
}

角度テストの場合、angleはテスト・ユーティリティーとテスト・フレームワークを提供します。テスト・フレームワークは、良いテスト・ケースを角度をもって記述するのに役立ちます。角型ユーティリティは@angular/core/testingからインポートでき@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();  

  });

});

上記の例では、コンポーネントが存在するかどうかテストケースを説明するテストケースが1つしかありません。上記の例では、 TestBedComponentFixtureような角度テストユーティリティが使用されています。

TestBedを使用して角度テストモジュールを作成し、このモジュールをconfigureTestingModuleメソッドでconfigureTestingModuleして、テストするクラスのモジュール環境を生成します。テストモジュールはbeforeEach関数でテストモジュールを設定する理由のすべてのテストケースの実行前に設定する必要があります。

TestBed createComponentメソッドは、テスト対象のコンポーネントのインスタンスを作成するために使用されます。 createComponentComponentFixture返しComponentFixture 。フィクスチャは、コンポーネントインスタンス自体へのアクセスを提供します。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow