angularJS App mit requireJS und Testacular testen

Wie schon ein paar mal geschrieben, bin ich schwer begeistert wie einfach es mit angularJS ist, Web Apps mit Javascript zu entwickeln. Vielleicht merkt man das auch an der ein oder anderen Stelle 🙂

Aber egal, wie einfach das Entwickeln ist, wir machen alle Fehler. Deshalb muss auch angularJS-Code getestet werden, mindestens Unit-Tests besser noch mit End-2-End-Tests.

[highlight1]Aber moment mal, Javascript-Code und Unit-Tests? Geht das überhaupt? [/highlight1]

Aber klar! Schon lang gibt es diverse Test-Frameworks für Javascript. jQuery benutzt z.B. qunit, die AngularJS-Entwickler bevorzugen Jasmine.

Als Skriptsprache, die beim Kunden im Browser ausgeführt wird, ist es bei Javascript-Tests natürlich wichtig, sie dort auszuführen wo sie laufen: im Browser. Und das ist ohne spezialisierte Tools äußerst aufwändig.

Als sogenannter Test Runner, gibt es seit einigen Jahren jsTestDriver (von Google), das Unit-Tests entgegennimmt und dafür sorgt, sie auf angeschlossenen Browsern auszuführen. Leider habe ich keine gute Erfahrung damit gemacht, da es allgemein recht instabil läuft, mehrfach täglich neugestartet werden muss und nach Neustart das automatische Wiederverbinden mit den Browsern eher hakelig ist. Außerdem scheint die Weiterentwicklung auch eher eingeschlafen zu sein.

Die AngularJS-Entwickler haben sich deshalb entschlossen, einen eigenen Test Runner zu entwickeln. [highlight1]Testacular[/highlight1].

Testacular ist kein Testframework, sondern wirklich nur der Test Runner. Man schreibt die Unit-Tests also z.B. als Jasmine-Tests und führt sie mit Testacular in Chrome oder Firefox oder Safari oder auch in PhantomJS (Headless Browser) aus.

Da wir AngularJS u.a. auch mit requireJS benutzen, ist es allerdings nicht ganz einfach, als Anfänger zu verstehen, wie man Testacular konfigurieren muss, damit requireJS den Produktions- und Testcode asynchron nachladen kann und erst danach Testacular loslegt um die Tests auszuführen.

Standardmässig liefert Testacular nämlich eine Webseite an den Browser aus, in der alle referenzierten *.js-Dateien automatisch als <script>-Tag referenziert werden, damit der Browser sie lädt. Nach Laden der Seite fängt dann Testacular an, die Tests auszuführen. Mit requireJS darf Testacular allerdings die Skripte eben gerade nicht mittels <script>-Tag laden, sondern dies übernimmt require.js. Nur die main.js um requireJS zu laden und zu konfigurieren, darf so eingebunden werden.

Als Startpunkt für meine Konfiguration mit requireJS habe ich einen Artikel bei Jake Trent gefunden, der sich genau diesem Problem widmet. Im Gegensatz zu diesem Artikel kann ich aber sagen, dass die komplette requireJS-Konfiguration wie man sie im Produktionsbetrieb gewohnt ist, auch für Testacular funktioniert. Testacular bindet mit meiner Konfiguration nur noch Jasmine, requireJS und die main-test.js ein. Alles andere lädt requireJS nach.

Dies erreicht man mit folgender Konfiguration der Dateien:

files = [
	JASMINE,
	JASMINE_ADAPTER,
	REQUIRE,
	REQUIRE_ADAPTER,
	{pattern:'src/main/external-libs/angular-1.0.3.js', included:false},
	{pattern:'src/test/external-libs/angular-mocks-1.0.3.js', included:false},
	{pattern:'src/main/js/**/*', included:false}, //Produktionscode
	{pattern:'src/test/unit/**/*.js', included:false}, //Testcode
	'src/test/main-test.js' //requireJS-Konfiguration
];

Entscheidend ist hier das [hightlight1]included:false[/highlight], was dafür sorgt, dass die Dateien zwar verfügbar sind, aber nicht per <script> automatisch ausgeliefert werden. Der Rest der Konfiguration kann so aus dem verlinkten Artikel übernommen werden.

In der main-test.js Datei muss man nun dafür sorgen, dass die eigene Anwendung geladen wird (die ja sowieso schon aus require-Module bestehen sollte), die Testklassen geladen werden (das müssen nun auch alles require-Module sein, siehe Artikel) und wenn das alles geladen ist, muss Testacular mitgeteilt werden, dass es nun loslegen kann. Meine main-test.js sieht wie folgt aus:

(function (window, require) {
	"use strict";
	var file, requireModules;
	requireModules = [];

	//aus den von Testaular bereitgestellten Dateien alle *Test-Dateien raussuchen und Pfad anpassen, damit requireJS
	//was damit anfangen kann
	for (file in window.__testacular__.files) {
		if (window.__testacular__.files.hasOwnProperty(file)) {
			if (file.substring(file.length - 7, file.length) === 'Test.js') {
				requireModules.push("../.." + file.substring(9, file.length - 3));
			}
		}
	}

	// unsere eigene Anwendung inkl. Abhängigkeiten laden
	requireModules.push("app");

	//angular Mocks laden
	requireModules.push("mocks");

	require({
		// "/base" ist die URL von der Testacular alle Dateien ausliefert,
		// "src/main/js" ist due Base-URL aller Module die nicht Test sind
		baseUrl:'/base/src/main/js',
		paths:{
			'angular':'../external-libs/angular-1.0.3',
			'mocks':'../../test/external-libs/angular-mocks-1.0.3'
		},
		shim:{
			'angular':{ exports:'angular' },
			'mocks':{ deps:['angular'], exports:'mocks' }
		}
	}, requireModules, function () {
		//erst hier wird Testacular tatsächlich (manuell) gestartet
		window.__testacular__.start();
	}, function (err) {
		//hier kommt die Fehlerbehandlung hin
	});
}(window, require));

Über den Zugriff auf [highlight1]window.__testacular__.files[/highlight1] hole ich mir alle Testdateien und bastele daraus die korrekt benamten require-Module. Dann wird noch die eigentliche Anwendung und Angular hinzugefügt. Wenn requireJS alles geladen hat, wird dann über [highlight1]window.__testacular__.start();[/highlight1] Testacular gestartet.

Wenn man das einmal begriffen hat, ist es eigentlich recht einfach. Aber das ist ja häufig so 🙂 Testacular ohne requireJS zu konfigurieren, ist auch deutlich einfacher.

Wenn es dann einmal läuft, hat man aber einen hervorragenden Testrunner, den man dann auch in Webstorm (Javascript IDE) integrieren kann, um direkt in der IDE, die Unit Tests starten zu können und – besonders beeindruckend – auch direkt in der IDE debuggen kann.

Aber dazu ein anderes mal mehr Details.

11 thoughts on “angularJS App mit requireJS und Testacular testen

  1. Hi Marco,
    zunächst mal: super Artikel. Ich habe es mal gemäß deiner Anleitung probiert. Bekomme aber leider immer einen Fehler. Wenn ich es starten will geht er in den folgenden Block:
    }, function (err) {
    //hier kommt die Fehlerbehandlung hin
    });

    und sagt: angular is not defined. Hast du eine Idee woran liegen könnte?

    1. Das könnte natürlich alles mögliche sein ohne den Code zu kennen, aber auf den ersten Blick sieht es so aus, als wenn keins deiner Module von angular abhãngt.
      Entweder du baust das so in eins deiner Module ein:
      define([‘angular’], function() {…});
      Oder du nimmst angular mit in die Liste explizit zu ladender Module mit auf:angularModules.push(“angular”);

      Die erste Variante würde ich präferieren wenn dein code sowiesovon Angular abhängt.

      1. Hi Marco,
        vielen Dank für deine Antwort! Ich habe mal ein Gist angelegt mit meiner Config und der main_test.js. Zu finden ist das ganze hier: https://gist.github.com/jhiemer/4731013

        Darin enthalten ist auch die Ordner Struktur. Wäre schön, wenn du nochmals 5 Minuten Zeit hättest um darüber zu schauen.

        Vielen Dank vorab!

        1. das einzige was mir erstmal auffällt ist, dass in der test-main.js die normale angular-Version geladen wird, in der normalen main.js jedoch angular-min. Ich gehe davon aus dass die nicht minimierte Version da auch liegt, oder?

          Ansonsten fällt mir nichts auf, das ist bei den relativen Pfadangaben aber auch kein Wunder. Was du noch machen kannst, ist die debug.html von testacular aufzurufen, normalerweise unter http://localhost:9876/debug.html Die ist im Browser komplett leer, aber wenn du in die Dev Console schaust, kannst du dir dort ein paar sachen anschauen. Z.B. gibt Testacular dort eine Liste aller Dateien aus, die er ausliefert (also window.__testacular__.files).
          Schau da mal rein, ob du die Pfadangaben alle richtig hinbekommen hast.

          1. Hi Marco,
            ich bedanke mich erneut für deine Hilfe. Leider habe ich es immernoch nicht zu fliegen gebracht. Habe nun mal die kleinstmögliche Variante meines Projekts auf github commited https://github.com/jhiemer/angularjs-requirejs-testacular, damit die ganzen sourcen vorhanden sind. (Ist nur ein Controller und sonst nichts). Was mache ich falsch? Bin langsam wirklich am verzweifeln… 🙁

          2. So, habs nun hinbekommen, dass die Tests erkannt werden. 🙂

            Allerdings laufen die Unit-Tests noch nicht durch, weil “before” nicht bekannt ist. Ich hab keine Ahnung welches Testframework das ist. In der Testacular.conf.js steht ja Jasmine, aber das kennt nur beforeEach und die nachfolgenden expect-Definitionen passen nicht zu Jasmine. Mein Webstorm meint es hätte qunit erkannt und will es als Abhängigkeit ins Projekt mit übernehmen. Soweit ich es gesehen hab, hat keine enthaltene Library “before” definiert. Müsstest du dir noch angucken.

            Ausserdem hast du noch DEN Kardinfalfehler begangen wenn du den strict-Modus verwenden willst: man schaltet den strict-Modus nicht global ein! Grund: er wird sonst wirklich glabal für ALLE eingebundenen Javascript-Dateien inkl. aller externen Frameworks aktiviert. Das willst du aber nicht. 🙂

    1. nein, das use strict war nicht das Problem, sondern die app.js war “schuld”.

      ich hab es eigentlich committet, mein Webstorm zeigt mir das zumindest an. In der GIT-History im Webstorm stehts drin, aber auf der Github Webseite nicht. Ich gebe zu, ich bin absoluter git/github Neuling. Muss ich nach dem commit noch was machen?

  2. I’m really impressedd ѡith ykur writing skills aas ԝell as with thee
    layout on your weblog. Iѕ thyis a paid theme or ԁiԀ уou modify іt уourself?
    Εither way kwep uup the excellent quality writing, іt iѕ rare to see ɑ nicfe blog like thіѕ оne nowadays.

    Feel free tߋ visit my webpage: Premium Cleanse; Gerardo,

Leave a Reply to Johannes Cancel reply

Your email address will not be published. Required fields are marked *

*