Recherche…


Installation du framework de test Jasmine

Le moyen le plus courant de tester les applications Angular 2 est d'utiliser le framework de test Jasmine. Jasmine vous permet de tester votre code dans le navigateur.

Installer

Pour commencer, tout ce dont vous avez besoin est le paquet jasmine-core (pas le jasmine ).

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

Vérifier

Pour vérifier que Jasmine est correctement configuré, créez le fichier ./src/unit-tests.html avec le contenu suivant et ouvrez-le dans le navigateur.

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

Mise en place de tests avec Gulp, Webpack, Karma et Jasmine

La première chose dont nous avons besoin est de dire à karma d'utiliser Webpack pour lire nos tests, dans une configuration que nous avons définie pour le moteur WebPack. Ici, j'utilise babel parce que j'écris mon code dans ES6, vous pouvez changer cela pour d'autres saveurs, telles que Typescript. Ou j'utilise les gabarits Pug (anciennement Jade), pas nécessaire.

Pourtant, la stratégie reste la même.

Donc, ceci est une configuration 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;

Et puis, nous avons besoin d'un fichier karma.config.js pour utiliser cette configuration 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
});
};

Jusqu'à présent, nous avons demandé à Karma d'utiliser webpack, et nous lui avons dit de commencer par un fichier appelé karma.shim.js . Ce fichier aura pour fonction de servir de point de départ pour Webpack. webpack lira ce fichier et utilisera l' importation et demandera des instructions pour rassembler toutes nos dépendances et exécuter nos tests.

Alors maintenant, regardons le fichier 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);

Essentiellement, nous importons TestBed à partir de tests de base angulaires et nous lançons l'environnement, car il ne doit être lancé qu'une seule fois pour tous nos tests. Ensuite, nous parcourons le répertoire src / app de manière récursive et lisons tous les fichiers qui se terminent par .spec.js et les transmettons à testContext afin qu'ils s'exécutent.

J'essaie généralement de mettre mes tests au même endroit que la classe. Personnalité, cela me permet d'importer des dépendances et des tests de refactorisation avec des classes. Mais si vous voulez placer vos tests ailleurs, comme dans le répertoire src / test par exemple, voici votre chance. changer la ligne avant dernière dans le fichier karma.shim.js.

Parfait. ce qui reste? ah, la tâche gulp qui utilise le fichier karma.config.js que nous avons créé ci-dessus:

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

Je suis en train de démarrer le serveur avec le fichier de configuration que nous avons créé, en lui disant de s'exécuter une fois et de ne pas surveiller les modifications. Je trouve que cela me convient mieux car les tests ne seront exécutés que si je suis prêt à les exécuter, mais bien sûr, si vous voulez des différences, vous savez où changer.

Et comme dernier exemple de code, voici une série de tests pour le tutoriel 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");
        })
    ));
});

Il est intéressant de noter que beforeEach () configure un module de test et crée le composant dans test, et comment nous appelons detectChanges () afin que angular passe réellement par la double liaison et tout.

Notez que chaque test est un appel à async () et qu'il attend toujours que promis stable soit résolu avant d'examiner le projecteur. Il a ensuite accès au composant via componentInstance et à l'élément via nativeElement .

Il y a un test qui vérifie le style correct. Dans le cadre du didacticiel, l'équipe angulaire démontre l'utilisation de styles à l'intérieur des composants. Dans notre test, nous utilisons getComputedStyle () pour vérifier que les styles proviennent de l'endroit spécifié, mais nous avons besoin de l'objet Window pour cela, et nous l'obtenons de l'élément comme vous pouvez le voir dans le test.

Test du service HTTP

Généralement, les services appellent Api distant pour récupérer / envoyer des données. Mais les tests unitaires ne devraient pas faire appel au réseau. Angular utilise en XHRBackend classe XHRBackend pour effectuer des requêtes http. L'utilisateur peut remplacer cela pour modifier le comportement. Le module de test angulaire fournit les classes MockBackend et MockConnection qui peuvent être utilisées pour tester et confirmer les requêtes http.

posts.service.ts Ce service frappe un noeud final api pour extraire la liste des publications.

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 Ici, nous allons tester le service ci-dessus en moquant les appels d'API http.

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

            })));
});

Test des composants angulaires - De base

Le code composant est donné ci-dessous.

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

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

Pour les tests angulaires, angular fournit ses utilitaires de test avec le framework de test, ce qui aide à écrire le bon scénario de test en angulaire. Les utilitaires angulaires peuvent être importés de @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();  

  });

});

Dans l'exemple ci-dessus, il n'y a qu'un seul scénario de test qui explique le scénario de test pour l'existence d'un composant. Dans l'exemple ci-dessus, des utilitaires de test angulaire tels que TestBed et ComponentFixture sont utilisés.

TestBed est utilisé pour créer le module de test angulaire et nous configurons ce module avec la méthode configureTestingModule pour produire l'environnement de module pour la classe que nous voulons tester. Module de test à configurer avant l'exécution de chaque test beforeEach c'est pourquoi nous configurons le module de test dans la fonction beforeEach .

createComponent méthode createComponent de TestBed est utilisée pour créer l'instance du composant sous test. createComponent renvoie le ComponentFixture . L'appareil donne accès à l'instance du composant lui-même.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow