20. Februar 2014 · von Steffen Nörtershäuser

Erklärt: JavaScript Unit Tests

Nachdem ich in zwei vorherigen Beiträgen bereits über Dynamics CRM 2013 Unit Tests geschrieben habe, möchte ich in diesem Artikel auf ein anderes Testverfahren eingehen. Eine immer wichtiger werdende Komponente in Webanwendungen: JavaScript. Wie funktionieren Javascript Unit Tests?

Die Unit Tests mit JavaScript werden anhand einer einfachen ASP .Net Anwendung erklärt. Innerhalb der Solution Mappe existiert neben dem ASP .Net Projekt ein Visual Studio Unit Test Projekt:

js_unittest_solution

Wie ein solches Unit Test Projekt angelegt werden kann, kann im Artikel CRM 2013 Unit Tests unter dem Abschnitt Anlegen des Unit Tests nachgelesen werden.

Der erste Ansatz: Unit Tests im Browser
Eine einfache Möglichkeit für den Test von JavaScript sind Unit Tests im Browser über JavaScript. Hierfür gibt es verschiedene Frameworks wie beispielsweise QUnit.

Mit QUnit kann man einfach und schnell Unit Tests für JavaScript erzeugen. Der entscheidende Nachteil von QUnit ist allerdings, dass es sich nicht ohne weiteres in den Visual Studio Test Prozess integrieren lässt.

Um den Unit Test ausführen zu können muss man den Browser geöffnet haben und zum Ausführen muss die Seite aktualisiert werden. Wäre es nicht schöner die JavaScript Tests ebenfalls im Visual Studio Test Explorer zu sehen und auszuführen zu können? Auf diese Weise könnten die CodeBehind sowie JavaScript Tests, beispielsweise für eine ASP .Net Anwendung, an einer Stelle ausgeführt und verwaltet werden.

Neben diesem zentralen Anlaufpunkt ist ein weiterer Vorteil dieser Vorgehensweise, dass sich die JavaScript Unit Tests in einem automatisierten Build Vorgang ausführen lassen. Es gäbe in so einem Fall für den Testbenutzer keinen Unterschied mehr zwischen einem JavaScript oder einem C# Unit Test.

In Visual Studio Integrierte JavaScript Unit Tests
Um das oben genannte Ziel zu erreichen ist es notwendig, die zu testenden JavaScript Dateien serverseitig auszuführen. Hierfür stehen verschiedene JavaScript Engines wie Rhino, V8 oder SpiderMonkey zur Verfügung. Für die in diesem Beitrag aufgezeigte Lösung habe ich mich für die vom Internet Explorer genutzte JScript Engine von Microsoft entschieden.

Diese lässt sich über zwei Möglichkeiten ansprechen:

  • Die Microsoft Windows Script Interfaces: Dies ist eine saubere Lösung, jedoch ist es kompliziert hierüber JavaScript auszuführen.
  • Als Alternative gibt es das COM-Control Microsoft Windows Script Control. Hierüber lässt sich JavaScript Code mit sehr geringem Aufwand ausführen.

Da es sich bei der Microsoft Script Control um die wesentlich einfachere und für unsere Zwecke ausreichende Alternative handelt, habe ich mich hierfür entschieden.

Windows Script Control
Um das Script Control nutzen zu können muss sie zu erst über den obigen Link heruntergeladen und installiert werden. Nachdem das Script Control installiert wurde muss eine COM Referenz auf diese Komponente in das Visual Studio Test Projekt hinzugefügt werden.

js_unittest_com_reference

Anschließend können diverse Hilfsklassen und Funktionen angelegt werden, damit in Zukunft schnell und einfach JavaScript Unit Tests erzeugt werden können. Hierfür legen wir zu Beginn einen neuen Ordner Helper innerhalb des Testprojektes an. In Diesen fügen wir nun eine Klasse „JsTestHelper“ mit folgendem Inhalt ein:

	/// <summary>
	/// Helper Class for Server Javascript Tests
	/// </summary>
	class JsTestHelper : IDisposable
	{
		/// <summary>
		/// Script Control for running JavaScript
		/// </summary>
		private ScriptControl _ScriptControl;

		/// <summary>
		/// Default Constructor
		/// </summary>
		public JsTestHelper()
		{
			_ScriptControl = new ScriptControl();
			_ScriptControl.Language = "JScript";
			_ScriptControl.AllowUI = false;
		}

		/// <summary>
		/// Loads a given file for Unit Testing
		/// </summary>
		/// <param name="filename">Filename with the content for unit testing</param>
		public void LoadFile(string filename)
		{
			string fileContent = File.ReadAllText(filename);
			_ScriptControl.AddCode(fileContent);
		}

		/// <summary>
		/// Executes a given Test Method
		/// </summary>
		/// <param name="testMethod">Test Method to execute</param>
		public void ExecuteTest(string testMethod)
		{
			try
			{
				_ScriptControl.Run(testMethod, new object[] { });
			}
			catch (Exception)
			{
				throw new AssertFailedException(((IScriptControl)_ScriptControl).Error.Description);
			}
		}

		/// <summary>
		/// Disposes the Object
		/// </summary>
		public void Dispose()
		{
			_ScriptControl = null;
		}
	}

Innerhalb dieser Klasse wird eine Instanz des erwähnten Script Control verwaltet. Dafür wird im Konstruktor lediglich eine neue Instanz der Script Control Klasse erzeugt und die Sprache auf JScript gesetzt. Die Zuweisung von AllowUI = false unterbindet UI Aufrufe, wie ein „alert“ aus JavaScript heraus. Würde dies nicht unterbunden werden könnte es passieren, dass die Unit Tests durch einen solchen Aufruf blockiert werden.

Die Funktion LoadFile lädt den Inhalt einer JavaScript Datei und fügt ihn an das Script Control an.

ExectueTest führt eine JavaScript Testmethode aus. Dabei werden eventuelle Fehler (primär ausgelöst durch ein fehlgeschlagendes Assert) aufgefangen und als eine AssertFailedException an den C# Unit Test weitergereicht.

Neben dieser C# Hilfsklasse bietet es sich an eine JavaScript Hilfsdatei für Assert Funktion anzulegen. Hierfür habe ich innerhalb des Helper Ordners eine neue JavaScript Datei „AssertHelper.js“ mit folgendem Inhalt angelegt:

if (typeof window == "undefined") {
	window = new Object();
}

if (typeof Assert == "undefined") {
	Assert = new Object();
}

(function (Assert) {
	/// Checks if two values are equal.
	Assert.AreEqual = function(expected, actual, message) {
		if (expected !== actual) {
			var errorMessage = "Expected value " + expected + " is not equal to " + actual + ".";
			if (typeof message != "undefined") {
				errorMessage += message;
			}

			throw new Error(errorMessage);
		}
	};
})(Assert);

Wie hier zu sehen ist wird innerhalb der Self-Executing Anonymous Function eine neue Funktion „AreEqual“ angelegt die prüft ob zwei Werte gleich sind. Ist dies nicht der Fall wird eine neue Exception geworfen. Diese Exception würde anschließend innerhalb unserer JsTestHelper Klasse in C# aufgefangen und als AssertFailed Exception an den C# Unit Test weitergereicht.

Das window Objekt wird hier definiert, da es standardmäßig innerhalb des Script Control nicht zu Verfügung steht. Falls eine zu testende JavaScript Datei an das window Objekt etwas binden möchte, dann käme es zu einem Fehler, wenn das window Objekt hier nicht definiert würde.

Wichtig ist hier, dass für alle Test Js-Datein die Eigenschaft „Copy to Output Directory“ auf „Copy always“ steht:

js_unittest_copy_always

Damit sind alle notwendigen Hilfsklassen umgesetzt und es kann an die Implementierung des eigentlichen Unit Test gehen.

Erzeugen der JavaScript Unit Tests
In unserem Beispiel wird folgende (zugegebenerweise sehr simple) JavaScript Datei aus dem ASP .Net Projekt getestet:

(function (DataAdapter) {
	DataAdapter.getUser = function (id, name) {
		var userObj = {};
		userObj.Id = id;
		userObj.Name = name;

		return userObj;
	};
})(window.DataAdapter = window.DataAdapter || {});

Zu dieser JavaScript Datei wird nun eine JavaScript Unit Test Datei mit dem Namen DataAdapterTest.js mit folgendem Inhalt angelegt:

function getUserTest() {
	// Arrange
	var id = "{9D2321C1-34EB-42FC-8BD2-73E64B4007CB}";
	var name = "Testname";

	// Act
	var userObj = window.DataAdapter.getUser(id, name);

	// Assert
	Assert.AreEqual(id, userObj.Id, "Id wurde nicht korrekt ermittelt.");
	Assert.AreEqual(name, userObj.Name);
}

Wie zu sehen ist, gibt es hier keinen großen Unterschied zu einem Unit Test aus C# heraus. In der ersten Phase werden die notwendigen Daten definiert und anschließend wird die zu testende JavaScript Funktion aufgerufen, so dass zum Schluss die Resultate mit der von uns angelegten Assert-Funktion getestet werden.

Nun muss dieser JavaScript Unit Test aus C# heraus aufgerufen werden.

Integrieren der JavaScript Unit Tests in Visual Studio
Hierfür wird eine C# Testklasse JsTest wie folgt erzeugt:

    /// <summary>
    /// Testmethod for the GetUser Function of the DataAdapter.js
    /// </summary>
    [TestMethod]
    public void DataAdapterGetUserTest()
    {
        using (JsTestHelper testHelper = new JsTestHelper())
        {
            // Load JavaScript
            testHelper.LoadFile("Helper/AssertHelper.js");
            testHelper.LoadFile("../../../JavaScriptUnitTest/DataAdapter.js");
            testHelper.LoadFile("DataAdapterTest.js");

            // Execute Test
            testHelper.ExecuteTest("getUserTest");
        }
    }

Wie hier zu sehen ist wird die vorher angelegte JsTestHelper Klasse genutzt, um die notwendigen Datein zu laden und anschließend der Test in JavaScript ausgeführt. Wie zu sehen ist wird zu Beginn die AssertHelper Datei, die zu testende Datei (aus dem ASP .Net Projekt) und abschließend der eben angelegte JavaScript Unit Test geladen. Wenn nun die Testfunktion getUserTest und der Assert im JavaScript aufgerufen wird, dann wird dieser Fehler weitergereicht und taucht wie gewohnt im Testexplorer auf:

js_unittest_result_error

Hier wurde für Demonstrationszwecke extra ein Fehler in die sample.js Datei eingefügt, wodurch eine fehlerhafte Id zurück gegeben wurde.

Fazit
Mit der hier vorgestellten Vorgehensweise ist es möglich Unit Tests für JavaScript wie gewohnt aus Visual Studio heraus auszuführen und sie bietet somit eine sehr gute Integration in den Visual Studio Testprozess.

Hierauf basierend können nun beispielsweise auch JavaScript Unit Tests für Dynamics CRM implementiert werden.



Diesen Blogeintrag bewerten:


Haben Sie Fragen zu diesem Artikel oder brauchen Sie Unterstützung?

Nehmen Sie mit uns Kontakt auf!

Wir unterstützen Sie gerne bei Ihren Vorhaben!


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Kontakt
Lassen Sie sich von uns beraten
Wir freuen uns über Ihr Interesse an unseren Leistungen. Hinterlassen Sie
uns Ihren Namen, Ihre Telefonnummer und E-Mail Adresse – wir melden
uns schnellstmöglich bei Ihnen.
Kontakt aufnehmen