Mock Depen­dencies in Unit-Tests mit TypeScript

Published On: 16. Juni 2020|Categories: Tech|
In vielen Kompo­­nenten- und Service-Tests versuchen wir die Abhän­gig­keiten zu mocken bzw. zu stubben.

In unserer Angular Anwendung Kredit­Smart nutzen wir zum Testen KarmaJasmine angerei­chert mit Chai und Sinon. In vielen Kompo­­nenten- und Service-Tests versuchen wir die Abhän­gig­keiten zu mocken bzw. zu stubben. Das kann schon mal einige extra Zeilen Quellcode mit sich bringen. Beim Zugriff auf die Stubs möchten wir zudem gerne Codever­voll­stän­digung seitens der IDE oder des Editors beim Zugriff und Typsi­cherheit haben. Der Stub soll die gleichen Methoden wie der gemockte Service haben. Als Rückga­bewert sollen die Methoden jedoch Sinon-Stubs liefern. Das ermög­licht uns in den Tests Rückga­be­werte zu definieren oder Prüfungen auf Metho­den­aufrufe durch­zu­führen. Die Typen der Service-Feldern können erhalten bleiben.

Typisierung

Der Typ unseres Mocks ist Mock<T>, wobei T der zu mockende Service oder eine andere Klasse sein kann. Die TypeScript Dekla­ration sieht folgen­der­maßen aus:

Copy to Clipboard

GenericMethod stellt eine beliebige Funktion dar. Gehen wir nun den ersten Teil der Typde­kla­ration von Mock<T> durch.

Copy to Clipboard

Hier wird über alle Properties K (also Felder und Methoden) des zu mockenden Services T iteriert. Durch -readonly wird die readonly-Eigen­schaft  sämtlicher Properties entfernt und wir können auch diese Felder im Test setzen.

Copy to Clipboard

Wenn Property K eine Methode (respektive Funktion) ist, so ist der Rückga­bewert vom Typ SinonStub. Andern­falls ist es ein Feld, dessen Typ aus dem Origi­nal­service übernommen wird.

Jedoch wrappen wir den Typ noch in ein Partial. Somit können wir bei Bedarf auch Methoden des Feldes mocken. In unserer Anwendung haben wir etwa Felder vom Typ RxJS-Subject und wollen dessen next-Methode für den Test stubben. Würden wir das Partial weglassen, müssten wir sämtliche Methoden von Subject stubben — das wollen wir nicht ;) .

Automa­ti­siertes Mocken aller Methoden

Um automa­tisch sämtliche Methoden einer Klasse zu mocken, bedienen wir uns der Hilfs­me­thode mock<T>:

Copy to Clipboard

Constructor<T> ist, wie der Name schon sagt, eine Konstruktor-Funktion. Im TypeScript Umfeld ist es also unsere Klasse, die wir Mocken wollen. Dies liegt daran, dass JavaScript’s Klassen­konzept nicht Klassen-basiert ist wie in Java oder C++, sondern Proto­­typen-basiert. Mehr zum Vergleich der beiden Konzepte. Die Verwendung von Klassen in TypeScript und auch neueren JavaScript-Versionen ist syntak­ti­scher Zucker.

Schauen wir uns die Dekla­ration der Methode mock<T> genauer an:

Copy to Clipboard

mock<T> erwartet also eine TypeScript-Klasse als Parameter und liefert einen Mock zurück. Ein Mock ist ein Objekt bei dem alle Methoden erhalten bleiben, jedoch als Rückga­bewert einen SinonStub liefern. Die Felder bleiben erhalten. Bei der Verwendung der Methode ist es jedoch nicht notwendig mock<MyService>(MyService) zu schreiben. Hier reicht mock(MyService) aus, da TypeScript T in der Funktion ableiten kann.

Copy to Clipboard

Die Imple­men­tierung iteriert daraufhin alle Properties der Klasse durch. Achtung — zur Laufzeit gehen Typin­for­ma­tionen verloren. Über den Prototyp eines Objektes können wir aber zumindest auf die Methoden des Objektes bzw. der Klasse zugreifen. Das reicht für unseren Fall aus, da die Imple­men­tierung der Klassen-Felder zur Laufzeit unver­ändert bleibt und wir nur die Typisierung zur Compile-Zeit brauchen.

Falls die obige Imple­men­tierung noch etwas funktio­naler sein soll, lässt sich die mock-Funktion auch folgen­der­maßen abändern:

Copy to Clipboard

updating


Anwendung

Wie sieht das ganze nun in der Anwendung aus? Bevor wir unsere Hilfs­kon­strukte hatten, sah das Setup in den Test folgen­der­maßen aus:


Copy to Clipboard

Mit unseren Konstrukten können wir das nun folgen­der­maßen schreiben:

Copy to Clipboard

Aufgrund von Type-Inference könnten wir sogar noch die Typde­kla­ra­tionen der beiden Services weglassen:

Copy to Clipboard

Nicht nur ist der Code weniger verbose, wir erhalten auch noch gute Codevor­schläge im jewei­ligen Editor und mehr Typsi­cherheit.

Credits

Die obigen Konstrukte sind in Zusam­men­arbeit mit Robin Baum und Marc Redemske entstanden.