Zoeken…


Het Jasmine-testraamwerk installeren

De meest gebruikelijke manier om Angular 2-apps te testen, is met het Jasmine-testraamwerk. Met Jasmine kunt u uw code in de browser testen.

Installeren

Om te beginnen heb je alleen het jasmine-core (geen jasmine ).

npm install jasmine-core --save-dev --save-exact

Verifiëren

Om te controleren of Jasmine correct is ingesteld, maakt u het bestand ./src/unit-tests.html met de volgende inhoud en opent u het in de browser.

<!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>

Testen opzetten met Gulp, Webpack, Karma en Jasmine

Het eerste wat we nodig hebben, is karma vertellen om Webpack te gebruiken om onze tests te lezen, onder een configuratie die we hebben ingesteld voor de webpack-engine. Hier gebruik ik babel omdat ik mijn code in ES6 schrijf, je kunt dat veranderen voor andere smaken, zoals Typescript. Of ik gebruik Pug-sjablonen (voorheen Jade), dat hoeft niet.

Toch blijft de strategie hetzelfde.

Dit is dus een webpack-configuratie:

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;

En dan hebben we een bestand karma.config.js nodig om die webpack-configuratie te gebruiken:

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

Tot nu toe hebben we Karma verteld om webpack te gebruiken, en we hebben gezegd dat het moet beginnen met een bestand met de naam karma.shim.js . dit bestand dient als startpunt voor webpack. webpack leest dit bestand en gebruikt de import en vereist instructies om al onze afhankelijkheden te verzamelen en onze tests uit te voeren.

Laten we nu eens kijken naar het bestand 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);

In wezen importeren we TestBed vanuit hoektests en initiëren we de omgeving, omdat deze slechts één keer hoeft te worden gestart voor al onze tests. Vervolgens gaan we recursief door de map src / app en lezen we elk bestand dat eindigt op .spec.js en voeren ze naar testContext, zodat ze kunnen worden uitgevoerd.

Ik probeer mijn tests meestal op dezelfde plek te zetten als de klas. Persoonlijk van smaak, het maakt het voor mij gemakkelijker om afhankelijkheden en refactor-tests met klassen te importeren. Maar als u uw tests ergens anders wilt plaatsen, bijvoorbeeld in de map src / test , dan is hier uw kans. wijzig de regel voor het laatst in het bestand karma.shim.js.

Perfect. wat is er over? ah, de gulp-taak die het karma.config.js-bestand gebruikt dat we hierboven hebben gemaakt:

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

Ik start nu de server met het configuratiebestand dat we hebben gemaakt, en vertel dat het een keer moet worden uitgevoerd en niet op wijzigingen moet letten. Ik vind dit beter voor mij omdat de tests alleen worden uitgevoerd als ik klaar ben om ze uit te voeren, maar als je anders wilt, weet je natuurlijk waar je moet veranderen.

En als mijn laatste codevoorbeeld, hier is een set tests voor de Angular 2-tutorial, "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");
        })
    ));
});

Opmerkelijk hierbij is hoe we beforeEach () een testmodule hebben geconfigureerd en de component in test hebben gemaakt, en hoe we detectChanges () aanroepen zodat hoekig door de dubbele binding gaat en zo.

Merk op dat elke test een aanroep van async () is en dat deze altijd wacht tot de stabiele belofte is opgelost voordat de armatuur wordt onderzocht. Het heeft dan toegang tot de component via componentInstance en tot het element via nativeElement .

Er is één test die de juiste styling controleert. als onderdeel van de tutorial demonstreert Angular team het gebruik van stijlen binnen componenten. In onze test gebruiken we getComputedStyle () om te controleren of stijlen afkomstig zijn van waar we hebben opgegeven, maar we hebben daarvoor het Window-object nodig en we halen het uit het element zoals u in de test kunt zien.

Http-service testen

Meestal bellen services externe Api om gegevens op te halen / te verzenden. Maar unit tests zouden geen netwerkoproepen moeten doen. Angular gebruikt XHRBackend klasse XHRBackend om http-aanvragen te doen. Gebruiker kan dit overschrijven om gedrag te veranderen. De MockBackend biedt MockBackend en MockConnection klassen die kunnen worden gebruikt om http-aanvragen te testen en te bevestigen.

posts.service.ts Deze service posts.service.ts een api-eindpunt om een lijst met berichten op te halen.

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 Hier zullen we de bovenstaande service testen door http api-oproepen te bespotten.

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

            })));
});

Hoekige componenten testen - Basic

De componentcode wordt gegeven zoals hieronder.

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

@Component({
  selector: 'my-app',
  template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
  title = 'welcome';
}

Voor hoektesten, biedt hoekig zijn testhulpprogramma's samen met het testkader dat helpt bij het schrijven van de goede testcase in hoekig. Hoekige hulpprogramma's kunnen worden geïmporteerd vanuit @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();  

  });

});

In het bovenstaande voorbeeld is er slechts één testcase die de testcase voor het bestaan van componenten verklaart. In het bovenstaande voorbeeld worden hulpprogramma's voor TestBed gebruikt, zoals TestBed en ComponentFixture .

TestBed wordt gebruikt om de TestBed te maken en we configureren deze module met de methode configureTestingModule om de TestBed te produceren voor de klasse die we willen testen. Testmodule die moet worden geconfigureerd voordat elke testcase wordt uitgevoerd, daarom configureren we de beforeEach in de functie beforeEach .

createComponent methode van TestBed wordt gebruikt om het exemplaar van het te testen component te maken. createComponent retourneert de ComponentFixture . De fixture biedt toegang tot de componentinstantie zelf.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow