[out of date]Angular2 – Testen mit Protractor


Die unten aufgeführten Beschreibung basiert auf einer veralteten Version von Angular. Einen aktualisierten Artikel gibt es hier:

https://javaeeblog.wordpress.com/2016/10/06/angular-2-testen-mit-protractor-update/

Protractor ist das Framework um Angular JS Anwendungen in einem End-To-End Test unter die Lupe zu nehmen. Zwar gibt es für Angular2 noch keine angepasste Version, sodass wir auf einige Features verzichten müssen, trotzdem ist auch Angular2 Bereit zum Test.

angular2_protractor2

Die Anforderung ist klar: wir wollen unsere Anwendung im Browser laufen lassen und Benutzer-Interaktionen simulieren um dann das Ergebnis zu prüfen. Unter der Haube von Protractor setzt auch dieses Framework dafür auf die bewährte WebDriver-API von Selenium. Selenium dient hier als Bindeglied zwischen unserem Test-Code und unserer Anwendung die im Browser abläuft. Die eigentlichen Tests werden schließlich mithilfe des Test-Frameworks „Jasmine“ zum Leben erweckt.

Projektsetup

Aber machen wir einen Schritt nach dem anderen und beginnen bei der Vorbereitung unseres Projektes. Zu allererst benötigen wir natürlich Protractor das mithilfe von npm schnell installiert ist:

 npm install –save-dev protractor

anschließend benötigen wir noch den bereits angesprochenen Selenium-Server der bei unseren Tests die Kommunikation zwischen dem Test-Code und den gewünschten Browsern übernimmt:

./node_modules/protractor/bin/webdriver-manager update

Für unser kleines Beispielprojekt verwenden wir Gulp als Task-Runner um unsere wiederkehrenden Aufgaben zu automatisieren. Um Protactor hier nahtlos zu integrieren verwenden wir ein entsprechendes Gulp-Modul welches ebenfalls installiert werden muss:

 npm install –save-dev gulp-angular-protractor

Um unsere Tests zum Ausführen zu bringen konfigurieren wir in unserem gulpfile.js nun einige Schritte die dafür Sorge tragen das unsere Anwendung kompiliert und alle statischen Ressourcen im Ordner „dist“ für ein Deployment zur Verfügung stehen. Anschließend starten wir einen lokalen Webserver gegen den unser Test ausgeführt werden soll bevor am Ende Protractor ans Werk gehen darf.

var protractor = require("gulp-protractor").protractor;
var connect = require('gulp-connect');

...

// Task kompiliert unsere Anwendung und startet einen Webserver auf Port: 3000
gulp.task('server', ['copy:deps', 'copy:src', 'compile:app'], function () {
    connect.server({
        root: 'dist',
        fallback: 'dist/index.html',
        port: 3000
    });
});

// Spezifiziert unsere Test-Sourcen und konfiguriert Protractor/Selenium
gulp.task('test', ['server:test'], function () {
    gulp.src(['test/**/*.js'])
            .pipe(protractor({
                configFile: "test/conf.js"
            }))
            .on('error', function (e) {
                throw e
            })
});

gulpfile.js
In unserem eigentlichen Task „test“ referenzieren wir ein Config-File das Protractor zur Initialisierung dient. Für unsere ersten Schritte genügt eine sehr sparsame Konfiguration die lediglich die Adresse des Selenium-Servers festlegt, den zu nutzenden Browser deklariert und Protractor für Angular2-Anwendungen konfiguriert.

exports.config = {
    seleniumServerJar: '../node_modules/protractor/selenium/selenium-server-standalone-2.52.0.jar',,
    useAllAngular2AppRoots: true,
    capabilities: {
  		'browserName': 'chrome'
	}
};

test/conf.js
Ein anschließendes „gulp test“ führt die gezeigten Schritte dann, inklusive der gefunden Test aus.

Zum Test bitte

In unseren eigentlichen Tests interagieren wir nun über den Selenium Server mit der laufenden Anwendung. Wir rufen also die zu testende URL auf, führen Aktionen aus und prüfen die Anzeige. Für das eigentliche Auffinden der Elemente stehen uns diverse so genannte Locators zu Verfügung die Elemente über Tag-Names, CSS, Texte und IDs ermittelt werden können. Wer schon Erfahrung mit dem Testen von Angular 1 hat wird allerdings einige Selektoren vermissen, so gab es z.B. die Möglich Selektionen aufgrund von NG-Model-Bindings zu ermitteln. Diese Angular-Spezifischen Selektoren werden leider (noch?) nicht Unterstützt und stehen somit erst einmal nicht zur Verfügung. Schauen wir uns ein einfachen Test an der unsere Start-Seite auf das vorhanden sein von unserer Navigation prüft:

describe('Home-Page', function () {
    beforeEach(function (done) {
        browser.get('http://localhost:3000/home').then(done());
    })

    it('Navigation Rendered', function () {
        expect(element(by.id('toForm')).getText()).toBe("Formular-Tests");
    });
});

test/components/home/home.js
Wir sehen einige Funktionen die „jasmine“ uns zur Verfügung stellt um unsere Tests zu schreiben:

describe“ um eine Test-Suite zu deklarieren um unsere Test zu gruppieren
beforeEach“ um in diesem Beispiel vor jeder Funktion eine frische Navigation auf unsere Seite durch zu führen
it“ um einen einzelnen Test zu deklarieren
expect“ um ein erwartetes Verhalten fest zu legen
Protractor liefert die entsprechenden Möglichkeiten Elemente auf zu finden
element“ dient zum Finden von Elementen
by.id“ Selector für die Suche nach ID
getText()“ Aktion die den Text des gefunden Elementes liefert

Ein wichtiger Punkt ist das alle Aktionen die mit/auf dem DOM des Browsers ausgeführt werden asynchron sind und damit einer so genannten Promise zurück liefern. Die „expect“ Methode akzeptiert solche Promises und wartet mit der Auswertung bis das Ergebnis der asynchronen Anfrage geliefert wurde. Wollen wir jedoch selber mit dem Ergebnis einer solchen Aktion weiter arbeiten ist es notwendig eine entsprechende Callback-Methode zu implementieren, die erst dann ausgeführt wird, wenn die Aktion abgeschlossen wurde. Hier ein Beispiel in dem wir Prüfen ob eine Navigation korrekt funktioniert oder der Submit Button eines Formulares erst nach der Eingabe eines Wertes aktiviert wird

it('Simple Navigation', function () {
    var button = element(by.id('toForm'));
    button.click().then(function () {
        expect(browser.getCurrentUrl()).toMatch(".*?/form");
    })
});

it("Submit Disabled", function () {
    var btn = element(by.css("button[type='submit']"))
    expect(btn.getAttribute('disabled')).toBeTruthy();
    element(by.id("message")).sendKeys("HelloWorld").then(function () {
        expect(btn.getAttribute('disabled')).toBeFalsy();
    })
})

TypeScript?

Die Beispiele bisher waren alle in puren JavaScript geschrieben. Wer seine Anwendung mittels TypeScript schreibt wird vermutlich auch die entsprechenden Test in dieser Form verfassen wollen. Die dafür benötigten Type Definitions liefert uns das npm – Modul „typings“ (npm install –g typings) mittels:

typings install --ambient --save selenium-webdriver
typings install --ambient --save angular-protractor
typings install --ambient --save jasmine
/// <reference path="..\..\..\typings\main.d.ts" />

describe('Home-Page', () => {
    beforeEach(() => browser.get('http://localhost:3000/home'))

    it('Navigation Rendered', () =>
        expect(element(by.id('toForm')).getText()).toBe("Formular-Tests")
    );
});

gulpfile.js
(im Gulp Script nicht vergessen die Datei-Endung der Tests auf „*.ts“ zu ändern)

Service – Test , Mocking

Ein klassischer End-To-End Test wird weitestgehend darauf verzichten Teile der Anwendung durch einen Test-Dummy zu ersetzen, schließlich geht es darum vollständige Prozesse, vom Frontend bis zur Datenbank, zu testen. Trotzdem kann es manchmal hilfreich sein den Angular Teil separat von eventuellen Java EE Backends zu testen. Für Unit Test steht uns bei Angular 2 die Klasse MockBackend zur Verfügung, die wir nutzen können um http-Requests ab zu fangen und innerhalb der Anwendung zu beantworten. Beim Testen mit Protractor steht uns diese Möglichkeit erst einmal nicht zur Verfügung, da wir ja eine fertige Anwendung testen die im Browser läuft und wir somit keinen Einfluss auf die injizierten Services nehmen können. Ein interessanter Kniff um hier Abhilfe zu schaffen ist der „connect-Server“ den wir im obigen Beispiel bereits dazu verwenden um unsere Anwendung für den Test zu deployen. Gulpfile.js

var endpoints_conf ={
    "/rest/posts/1": "test/data/post.json"
};

gulp.task('server:test', ['copy:deps', 'copy:src', 'compile:app'], function () {
    connect.server({
        root: 'dist',
        fallback: 'dist/index.html',
        port: 3000,
        middleware: function (connect, options) {
            var middlewares = [];
            middlewares.push(function (req, res, next) {
                var endpoints = endpoints_conf;
                var match = false;
                var fileToRead = "";

                Object.keys(endpoints).forEach(function (url) {
                    if (req.url.indexOf(url) == 0) {
                        match = true;
                        fileToRead = endpoints[url];
                    }
                });

                if (match == false) {
                    return next();
                }

                res.end(fs.readFileSync(fileToRead));
            });

            return middlewares;
        }
    });
});

gulpfile.js
Was tun wir? Wir erzeugen einen neuen Task der einen Server startet der speziell für die Tests konfiguriert ist. Verwendet wir das Attribut „middleware“ dem wir eine Funktion zuweisen die für jeden Request aufgerufen wird der an diesen Server geht. Basierend auf den vorher deklarierten Pattern prüfen wir nun ob es sich bei dem gestellten Request um eine Anfrage handelt die wir mocken wollen. Ist dies der Fall liefern wir feste Testdaten aus ohne eine laufende Rest-API an zu sprechen. Damit entkoppeln wir den Protractor Test von einem eventuell benötigten Java EE Server der Rest-Services anbietet oder anderen externen Quellen.
Beispiel Projekt: https://github.com/GEDOPLAN/angular2-protractor
Beispiel Projekt (TypeScript Tests): https://github.com/GEDOPLAN/angular2-protractor/tree/TypeScriptTest

Wie wir sehen konnten ist das Testen mittels Protractor nicht schwer, dank Jasmin, Selenium und Gulp ist ein solcher Prozess schnell aufgebaut und die ersten Tests implementiert. Neben den hier gezeigten Möglichkeiten bietet Protractor und Jasmin noch einige weitere Features wie das Generieren von Reports oder sogar das Erstellen von Screenshots nach jedem Test. Mehr dazu finden Sie auf den entsprechenden Seiten der Hersteller.

Advertisements

Über Dominik Mathmann
Dominik Mathmann arbeitet als Berater, Entwickler und Trainer bei der GEDOPLAN GmbH. Auf Basis seiner langjährigen Erfahrung in der Implementierung von Java-EE-Anwendungen leitet er Seminare, hält Vorträge und unterstützt Kunden bei der Konzeption und Realisierung von Webanwendungen vom Backend bis zum Frontend. Sein derzeitiger Schwerpunkt liegt dabei auf der Entwicklung von JSF-Anwendungen. Er sieht die stärker werdende Position von JavaScript-Frameworks jedoch positiv und beschäftigt sich darüber hinaus mit Webframeworks wie AngularJS.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: