Beliebte Suchanfragen
//

Mule4: Maßgeschneiderte Assertions mit MUnit Custom Matchers

1.5.2024 | 4 Minuten Lesezeit

Jeder, der über einen längeren Zeitraum mit Mule gearbeitet hat und (hoffentlich!) MUnit-Tests geschrieben hat, ist vermutlich auch schon einmal auf die so genannten Matcher gestoßen. Falls nicht, ist das auch nicht schlimm.

Matcher sind im Prinzip Helferfunktionen, die es ermöglichen, sowohl einfache als auch sehr komplexe Assertions durchzuführen.

So kann man zum Beispiel mit der folgenden Assertion prüfen, ob alle Werte in einem Array eine Nummer sind:

%dw 2.0
import dw::tests::Asserts
⁠---
⁠[1,2,3] must eachItem(beNumber())

Oder mit dem Folgenden, ob ein Key in einem Objekt existiert:

%dw 2.0 
import dw::tests::Asserts
---
{ name: “Pasquale”, lastName: “Brunelli” } must haveKey("name")

Sollte eine Assertion nicht erfüllt sein, schlägt der Testfall entsprechend fehl.

Insgesamt gibt es in Mule einige Build-in-Matcher, die das Erstellen von Assertions in MUnit-Tests wesentlich vereinfachen können.

In diesem Blogpost soll es aber gar nicht um die Build-in-Matcher gehen, sondern um die Möglichkeit, eigene Matcher zu erstellen. In meinen Projekten fällt mir dabei immer wieder auf, dass von dieser Möglichkeit kaum Gebrauch gemacht wird, obwohl es durchaus sinnvolle Szenerien für Matcher gibt. Stattdessen werden regelmäßig mehr oder weniger komplexe Assertions von einem Test in den nächsten kopiert und dabei vor allem Wart- und Lesbarkeit stark reduziert.

Ein Beispiel

Nehmen wir zum Beispiel einmal an, wir wollen validieren, dass unser Prozess tatsächlich valide (deutsche) KFZ-Kennzeichen erstellt. Dafür wird erwartet, dass wir für eine größere Anzahl von Kennzeichen MUnit-Tests schreiben, um sicherzustellen, dass keine irregulären Kennzeichen erzeugt werden können.

Unter Zuhilfenahme einer Regular Expression prüfen wir also verschiedene Beispielkennzeichen, ob diese valide sind. In jedem dieser MUnit-Tests haben wir dann, je nach Anzahl der nötigen Testfälle, eine mehr oder weniger große Anzahl an MUnit-Flows, die alle die Gleiche – aber nicht dieselbe Regular Expression für diesen Zweck nutzen.

%dw 2.0
output application/java 
---
payload matches /^[A-ZÖÜÄ]{1,3} [A-ZÖÜÄ]{1,2} [1-9]{1}[0-9]{1,3}$/

Wenn sich die Regular Expression nun verändert (was bei Nummernschildern zugegebenermaßen eher selten vorkommt), müsste man diese RegEx an mehreren Stellen anpassen. Auch das Verständnis, was genau der Code abbildet, ist für andere Kollegen, die an dem Code arbeiten, nicht unmittelbar gegeben.

Da wäre es doch schöner, wenn dieses Skript stattdessen wie folgt aussehen würde:

%dw 2.0
import dw::tests::Asserts
---
"MAX MU 2000" must beNummernschild()

Da sieht dann wirklich jeder Betrachter direkt, was passiert, oder?

Wie geht das nun?

Ein Custom-Matcher ist ganz einfach erstellt.

Zuerst erstellen wir eine Datei im “src/test/resources/dwl/matcher”-Ordner unseres Mule-Projektes. Diese nennen wir NummernschildMatcher.dwl.

In diese können wir folgenden Code kopieren:

%dw 2.0
import * from dw::test::Asserts

fun beNummernschild(): Matcher<String> =
     (actual:String) -> do {
         var isNummernschild = actual matches (/^[A-ZÖÜÄ]{1,3} [A-ZÖÜÄ]{1,2} [1-9]{1}[0-9]{1,3}$/)
         ---
         {
             matches: isNummernschild,
             description: {expected: " to be an Nummernschild.", actual: write(actual) as String}
         }
     }

Wir haben nun eine Funktion beNummernschild() definiert, die einen String als Parameter erwartet und diesen dann gegen unsere Regular Expression validiert und das Ergebnis als Boolean in der Variable isNummernschild ablegt.

Als Rückgabe wird der Wert matches erwartet. Dieser erhält das Resultat aus der vorangegangenen Validierung (Natürlich hätte man diese auch ohne die Nutzung einer Variable direkt in das Feld schreiben können!). Das Object description wird beim Ausführen der Tests genutzt, wenn die Assertion fehlgeschlagen ist.

Jetzt ist es bereits so weit und wir können unseren Nummernschild-Matcher innerhalb unserer MUnit-Testfälle nutzen, um herauszufinden, ob wir im Payload ein reguläres Nummernschild erhalten haben:

import * from dw::test::Asserts 
---
payload must dwl::matcher::NummernschildMatcher::beNummernschild()

Das Verhalten können wir natürlich auch invertieren, wenn wir kein Nummernschild erwarten:

import * from dw::test::Asserts 
---
payload must notBe(dwl::matcher::NummernschildMatcher::beNummernschild())

Die zugehörigen Flows sehen dann so aus:

Es ist aber auch möglich, unsere Assertion mit existierenden Funktionalitäten zu mischen, um so zum Beispiel mehrere Werte gleichzeitig zu überprüfen. Sollte hier ein Element des Arrays keinem Nummernschild entsprechen, liefert eachItem ein Array mit dem Index des bzw. der fehlerhaften Elemente.

import * from dw::test::Asserts 
---
payload must eachItem(dwl::matcher::NummernschildMatcher::beNummernschild())

Fazit

MUnit Custom-Matcher sind keine Raketenwissenschaft – und mein Beispiel natürlich auch nicht. Vielmehr finde ich es wichtig, dass man sich der Möglichkeiten, die Mule bereitstellt, bewusst ist und somit die besten Vorraussetzungen hat, verständlichen, wartbaren und – daraus resultierend – schönen Code zu schreiben.

Ab wann es sich für eure Testfälle lohnt, müsst ihr natürlich selber abwägen. Ich bleibe jedoch dabei, dass ich MUnit-Tests, bei denen ich Custom Matcher benutze, mit gutem Gewissen an meine Kunden übergeben kann.

Als nächsten Schritt, wenn es zum Beispiel branchenspezifische Muster gibt, die in vielen verschiedenen Projekten genutzt werden sollen, könnte man seine Matcher in ein DataWeave Library Projekt auslagern und somit dem gesamten (Teil-)Projekt oder gar Unternehmen eine einheitliche Basis an Matchern zur Verfügung stellen. Spätestens dann ergibt es auch Sinn, eigene Tests für die Matcher zu schreiben, um die Korrektheit sicherzustellen.

Ich bedanke mich für euer Interesse. Bei Fragen oder Kritik dürft ihr mich wie immer gerne kontaktieren!

Beitrag teilen

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.