© DrHitch/Shutterstock.com
Moderne Webanwendungen mit AngularJS

3 Testgetriebene Entwicklung mit AngularJS


AngularJS ist ein JavaScript-Framework für die Entwicklung von modernen clientseitigen Webanwendungen. Erstmalig halten bewährte Konzepte wie Dependency Injection und konsequente testgetriebene Entwicklung Einzug in die JavaScript-Welt und machen diese somit attraktiv für den Enterprise-Einsatz. Das Framework bietet hier einige sehr intuitive APIs und achtet in hohem Maße auf das Einhalten und Bilden von Schnittstellen.

In den beiden vorangegangenen Ausgaben haben wir bereits ein AngularJS-Projekt mit dem Namen Cube angelegt. Cube ist eine einfache Anwendung zur Visualisierung eines dreidimensionalen Würfels, bei dem wir den X-, Y- und Z-Rotationsgrad steuern können. In der vorherigen Ausgabe haben wir das Projekt so erweitert, dass Würfel nun auch dynamisch erstellt und wieder gelöscht werden können. Außerdem gibt es die Möglichkeit, die sechs Seiten eines Würfels mit thematischen Hintergrundbildern auszustatten (Abb. 3.1). Wir haben uns dafür entschieden, diese Bilder über das Flickr-API zu beziehen. Aus der technischen Perspektive betrachtet, haben wir uns also bereits die folgenden AngularJS-Konzepte angesehen: Zwei-Wege-Datenbindung, Templates, Scopes, Controller, Direktiven und Services.

In dieser Ausgabe wollen wir in erster Linie anschneiden, welche Möglichkeiten uns das Framework bietet, um testgetrieben entwickeln zu können. In AngularJS wird das Thema der Testbarkeit nämlich ganz groß geschrieben. Der Frameworkcode selbst zeigt eine außerordentlich hohe Testabdeckung auf, und auch alle Anwendungskomponenten wurden so konzipiert, dass wir sie sehr gut testen können. Die Ausrede, dass clientseitige Webanwendungen schwierig zu testen seien, greift also nicht mehr.

Bevor wir uns an das Testthema wagen, wollen wir unsere Cube-Anwendung zunächst jedoch noch um zwei Aspekte erweitern. Dazu werden wir im ersten Schritt unseren flickr-Service mithilfe eines Serviceproviders definieren und den Service somit konfigurierbar machen. Außerdem werden wir Routen einbauen, um zu zeigen, wie wir aus einer AngularJS-Anwendung eine vollwertige Single-Page-Applikation machen können.

Klassen in JavaScript

In JavaScript können wir mithilfe von Funktionen und Prototypen ein Konstrukt nachbilden, das einer Klasse ziemlich nahekommt. Innerhalb einer Klasse, die mithilfe einer Funktion definiert wurde, können wir – genauso wie in Java – mit this auf das entsprechende Objekt zugreifen. Allerdings funktioniert das nur, wenn die Funktion tatsächlich in Verbindung mit dem new-Operator aufgerufen wird. Solche Funktionen nennen wir auch Konstruktorfunktionen. Wenn eine Konstruktorfunktion ohne den new-Operator aufgerufen wird, dann setzt die Laufzeitumgebung den Wert von this innerhalb der Funktion auf das globale Objekt, was im Falle eines Browsers das window-Objekt ist. Mit speziellen JavaScript-Funktionen wie call() und apply() haben wir allerdings die Möglichkeit, den Wert von this beim Aufruf auch manuell festzulegen.

Serviceprovider und Konfiguration

In der vorangegangenen Ausgabe haben wir den flickr-Service implementiert, der die HTTP-Kommunikation zum Flickr-API kapselt und uns somit einen einfachen Zugriff auf die Hintergrundbilder erlaubt. Um den Servicebegriff nicht unnötig kompliziert einzuführen, haben wir uns dort für die einfache Definition mithilfe einer Service-Factory (factory()) entschieden. Das ist die häufigste Art, einen Service zu definieren. Allerdings hat sie einen entscheidenden Nachteil. Services, die mit einer Factory definiert werden, können nicht konfiguriert werden. Und wenn wir nochmals über unseren flickr-Service nachdenken, sollte uns auffallen, dass es zumindest eine Eigenschaft gibt, für die sich eine Konfiguration anbietet, nämlich für den API-Key für das Flickr-API. Somit wollen wir uns nun anschauen, wie wir die bestehende Factory (flickrService.js) in einen Provider überführen können, bei dem der API-Key konfigurierbar ist.

tarasiewicz3_1_fmt.png

Abb 3.1: Stand des Cube-Projekts aus dem zweiten Teil

Den Quellcode für den Serviceprovider sehen wir in Listing 3.1. Wir können erkennen, dass wir einen Provider mit der provider()-Funktion von AngularJS einführen können. Wie alle anderen Komponenten zuvor auch definieren wir den Provider in unserem Anwendungsmodul cubeApp. Auch die provider()-Funktion erwartet zwei Parameter. Als ersten Parameter geben wir den Namen des Service an, den wir mithilfe des Providers erstellen wollen. Mit dem zweiten Parameter legen wir die Konstruktionsvorschrift des Providers fest, indem wir eine anonyme Funktion übergeben. Wir können nachvollziehen, dass wir innerhalb der anonymen Funktion mit this arbeiten. Das liegt daran, dass AngularJS die anonyme Funktion intern als „Klasse“ betrachtet und somit mithilfe des new-Operators ein neues Providerobjekt von dieser Klasse erzeugt.

angular.module("cubeApp")
.provider("flickr", function () {
var API_KEY;

this.apiKey = function (apiKey) {
if (apiKey) {
API_KEY = apiKey;
}
return apiKey;
};


this.$get = function ($http) {
var getPhotosByTagFn = function (tag) {
return $http.get("http://api.flickr.com/services/rest/", {
params: {
method: "flickr.photos.search",
api_key: API_KEY,
[...]
}
})
.then( [...] );

};

// Public API
return {
getPhotosByTag: function (tag) {
return getPhotosByTagFn(tag);
}
};
}
});

Listing 3.1: flickrService.js

Es gilt die Konvention, dass AngularJS letztendlich den Teil als Service registriert, den der Provider in seiner $get()-Funktion zurückgibt. Das bedeutet, dass die $get()-Funktion praktisch der anonymen Funktion entspricht, die wir in der vorangegangenen Ausgabe der factory()-Funktion als zweiten Parameter übergeben haben. Jede andere Funktion, also in diesem Fall lediglich die apiKey()-Funktion, dient zur Konfiguration und kann somit nur in der Konfigurationsphase aufgerufen werden. Mit der apiKey()-Funktion können wir in der Konfigurationsphase also abhängig vom übergebenen Parameter lesend bzw. schreibend auf den API-Key zugreifen. Das ist im Übrigen ein Muster, das wir häufig in der JavaScript-Welt wiederfinden werden. Ein und dieselbe Funktion wird also als Setter wie auch als Getter genutzt.

angular.module("cubeApp")
.config(function (flickrProvider) {
flickrProvider.apiKey("4e0e99bf242015aee01bffea1efff314");
});

Listing 3.2: flickrConfig.js

Jetzt haben wir mithilfe des Providers eine Möglichkeit geschaffen, um unseren flickr-Service konfigurieren zu können. Was bleibt, ist die Frage, wie denn nun die eigentliche Konfiguration des API-Keys durchgeführt werden kann.

Maßgeblich dafür sind die so genannten config()-Blöcke (Listing 3.2), die AngularJS in der Konfigurationsphase ausführt. In einem config()-Block können wir uns per Dependency Injection die zu konfigurierenden Provider übergeben lassen, um die entsprechenden Konfigurationsfu...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang