11. Dez 2023
Lesedauer 15 Min.
Mit bUnit Blazor/Razor-Komponenten testen
Testautomatisierung für .NET-Apps, Teil 4
Das Framework bUnit ermöglicht das automatische Testen von Blazor-Komponenten und Razor-Pages mittels Modul/Unit- und Integrationstests.

Im Zentrum des Full-Stack-Frameworks Blazor von Microsoft steht die Entwicklung von Apps auf der Basis von .NET [1] mit C# (anstelle von JavaScript) und HTML/CSS. Microsoft stellt Blazor als Open-Source-Produkt in mehreren Editionen bereit [2]. Zwei davon (Blazor Native, Blazor United; siehe Kasten Zwei weitere bereits angekündigte Blazor-Editionen) hat Microsoft bisher lediglich angekündigt, aber noch nicht freigegeben. Aus Architektursicht erfolgt die Programmierung von Blazor-Apps auf der Basis von Komponenten.
Listing 4: Die bUnit-Testklasse MyComponentTests
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MeinService</span><br/>{<br/> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetMessage</span>(<span class="hljs-params"></span>)</span><br/><span class="hljs-function"> </span>{<br/> <span class="hljs-keyword">return</span> <span class="hljs-string">"Guten Tag sendet MeinService!"</span>;<br/> }<br/>}
Abgrenzung von Blazor, Razor und ASP.NET-Core-Webseiten
Bei einer Blazor-Komponente handelt es sich um eine in der Razor-Syntax programmierte User-Interface-Komponente, deren Quellcode sich in einer mit C# und HTML codierten .razor-Datei befindet. Man spricht dabei gelegentlich auch von einer Razor-Komponente. Ursprünglich (vor der Konzeption von Blazor) zielte Razor ausschließlich auf die Entwicklung von ASP.NET-Core-Webseiten ab. Dazu stellt Razor eine eigene Template-Markup-Syntax für C# bereit [3]: Ein Programmierer codiert den Quellcode einer ASP.NET-Core-Webseite in einer .cshtml-Datei – der sogenannten Razor-Page. Razor unterstützt die Entwicklung von Web-Apps auf Basis des Architektur-Patterns MVC (Model-View-Controller), den Einsatz von ASP.NET Web Forms für Web-Apps und die Programmierung von RESTful-Webservices mit dem ASP.NET Web API.In Anlehnung an gängige JavaScript-Template-Engines erzeugt Razor/Blazor aus dem als Markup in der .cshtml-Datei stehenden Quellcode eine HTML/CSS-Datei. Dieser Vorgang, auch Rendering genannt, findet auf der Seite des Webservers statt: Der in Razor integrierte Compiler überführt auf der Serverseite .cshtml-Dateien in reinen HTML/CSS-Quellcode – dabei kommt standardmäßig Server-Side Rendering (SSR) zum Einsatz. Diese HTML/CSS-Datei reicht der Webserver unverändert an den im Webbrowser laufenden Client weiter. Um die HTML/CSS-Datei anzuzeigen, verwendetder Webbrowser auf dem Client das zugehörige Document Object Model (DOM). Zusätzliche Logik auf dem Server legt der Programmierer in einer sogenannten Code-behind-Datei mit gleichem Namen wie die Razor-Page und dem Dateityp .cshtml.cs ab. Abhängig vom Typ der Razor/Blazor-App stehen verschiedene Rendering Modes, Host-Modelle und Deploy-Verfahren zur Verfügung.Damit verfügt Razor neben einer eigenen Markup-Syntax auch über ein Tooling, das auf dem Webserver und in der Entwicklungsumgebung läuft. Neben dem Layout mittels CSS und zentralen Layout-Pages unterstützt Razor auch Kontrollstrukturen wie if, else, else if oder switch. Mittels Validation Attributes referenziert der Programmierer in einer Razor-Page Integritätsbedingungen für Eingabedaten, die ASP.NET Core/MVC über das Datenmodell prüft. Ergänzend besitzt Razor ein Library-Konzept (RCL/Razor Class Library) für die Wiederverwendung von Komponenten in verschiedenen Projekten. Ursprünglich als Bestandteil des AspNetWebStack konzipiert, hat Microsoft Razor inzwischen in das ASP.NET-Core-Framework integriert.Als weitere Komponente von ASP.NET Core kam Blazor zunächst für die Unterstützung von WebAssembly/Wasm hinzu. Inzwischen hat Microsoft Blazor zu einem kompletten Full-Stack-Framework für die Anwendungsentwicklung weiterentwickelt (Bild 1). So entspricht eine Blazor-Server-App einer Web-App, die als Thin/Remote-Client über ASP.NET Core im ASP.NET-Razor-Format gehostet wird. Bei einer Blazor-WebAssembly-App handelt es sich um eine Single-Page-App (SPA), die einen Webbrowser voraussetzt, der WebAssembly verarbeitet. Blazor PWA unterstützt die Programmierung progressiver Web-Apps, während Blazor Hybrid (.NET MAUI Blazor Hybrid) auf die Ausführung der Blazor-App auf mobilen Geräten mit WebView2 abzielt.

Das Full-Stack-Framework Blazor führt auf dem Server abhängig vom zugrunde liegenden Typ der Blazor-App das jeweils benötigte Rendering durch (Bild 1)
Autor
NuGet-Package bunit.template nicht zielführend
Der Einsatz des NuGet-Packages bunit.template, wie in der Dokumentation von bUnit beschrieben, erweist sich in praktischen Projekten nicht als empfehlenswert.
bUnit fokussiert auf das Testen von Blazor-Komponenten im Unit-Sinne
Im Jahr 2019 konzipierte Steve Sanderson das Test-Framework BlazorUnitTestingPrototype mit dem Ziel, Unit-Tests für eine Blazor-App möglichst einfach und produktiv zu schreiben. Sanderson legte bei dem von ihm entwickelten Prototyp besonderen Wert auf die Unabhängigkeit der Blazor-Komponententests von der eigentlichen App und dem Webbrowser. Im Unterschied zu gängigen Testing-Frameworks für Web-Apps wie Selenium, Playwright oder Puppeteer sollte das Testing-Framework für Blazor keine über einen Webbrowser laufende Web-App oder gar ein JavaScript-Laufzeitsystem benötigen. Diesen zentralen Gedanken griff Egil Hansen bei der Weiterentwicklung des Prototyps zu bUnit [4] auf.Die Programmierung von Komponententests mit bUnit erfolgt mittels C# und der Razor-Syntax. Das Rendering einer Blazor-Komponente findet innerhalb des Testing-Frameworks statt. Anschließend führt bUnit die Komponententests auf diesem Rendering-Output aus.Aufgrund dieses minimalistischen oder leichtgewichtigen Testing-Ansatzes entwickelte sich bUnit zu einem Quasi-Standard für das Testen von Blazor-Komponenten. Der mit den anderen Testing-Tools verbundene Overhead wie Starten der Web-App, des Webbrowsers oder der JavaScript-Engine reduziert sich mit bUnit auf wenige absolut für Unit-Tests notwendige Arbeitsschritte. Grundsätzlich gliedert sich jeder Unit-Test in drei Bestandteile:- Arrange: Bereitstellen der zu testenden Blazor-Komponente gemäß den Anforderungen des gegebenen Ausgangsszenarios.
- Act: Durchführung der für den Komponententest notwendigen Aktionen.
- Assert: Prüfen, ob das vorliegende Ergebnis der Ausführung des Unit-Tests mit den erwarteten Ergebnissen übereinstimmt. Dabei erlaubt ein Unit-Test mehrere Assert-Prüfungen innerhalb eines Testfalls.
Unit-Testing-Framework als Basis für Komponententests mit bUnit nutzen
Primär benötigt ein Tester für die Ausführung von Unit-Tests mit bUnit die Installation eines der Unit-Testing-Frameworks MSTest, NUnit oder xUnit. Der Einsatz eines der genannten Unit-Testing-Frameworks erleichtert aus mehreren Gründen das Testen von Blazor/Razor-Komponenten. So unterstützt ein Unit-Testing-Framework vor allem die Strukturierung, die Organisation und das Management der Testfälle. Anhand der vom Unit-Testing-Framework bereitgestellten Features erstellt der Tester leicht Testklassen, -methoden, -Fixtures und -attribute.Ein Test-Fixture hilft bei der Bereitstellung eines konsistenten Zustands vor und nach der Ausführung eines Komponententests. Es isoliert Komponententests voneinander, ermöglicht eine zuverlässige Testausführung und gewährleistet vorhersagbare Testergebnisse. Ein Testattribut entspricht einer Markierung oder Auszeichnung, die der Tester mit einer Testmethode verknüpft. Testattribute ermöglichen die Konfiguration und Parametrisierung von Testfällen – über diese steuert das Unit-Testing-Framework die Ausführung der Testfälle.Des Weiteren besitzt ein Unit-Testing-Framework die beiden Features Discovery und Execution. Bei diesen handelt es sich um inhärente Mechanismen, um vorhandene Testfälle zu lokalisieren und automatisch auszuführen. Für beide Aufgaben stellt das Unit-Testing-Framework ein eigenständiges CLI (Command Line Interface) zur Verfügung. Die Prüfung von Aussagen über das Verhalten des Quellcodes erfolgt über die Definition von Test-Assertions. Das Unit-Testing-Framework besitzt dazu spezielle Assert-Methoden, welche die Ausgabe der Ausführung eines Testfalls mit den zu erwartenden Ausgabewerten überprüft.Für den Einsatz eines Unit-Testing-Frameworks spricht auch ein fertig implementiertes Berichtswesen und dessen Integration in ein CI/CD-System (Continuous Integration / Continuous Delivery) (Bild 2). Mithilfe des implementierten Reporting erzeugt das Unit-Testing-Framework Berichte auf Basis der durch die Testausführung erhaltenen Ergebnisse. Dieses Berichtswesen erleichtert die automatische Ausführung von Tests, da anschließend mögliche Problemfälle direkt identifiziert werden. Erst die Integration des Unit-Testing-Frameworks in ein CI/CD-System gewährleistet, dass Regressionstests bei Änderungen im Quellcode der App automatisch ausgeführt werden.
CI/CD bietet für die Qualitätssicherung von Software viele Vorteile (Bild 2)
Autor
Infrastruktur für Unit-Testing in einem Blazor-App-Projekt bereitstellen
Die Infrastruktur für Unit-/Komponententests einer Blazor-App besteht aus zwei Bestandteilen: einem Unit-Testing-Framework (MSTest, NUnit, xUnit) und dem bUnit-Test-Framework. Der Einsatz von xUnit empfiehlt sich‚ da es als das neueste Unit-Testing-Framework einige Verbesserungen gegenüber den beiden älteren aufweist. Im Unterschied zu MSTest und NUnit wurde xUnit speziell für .NET entworfen und nicht wie diese von einer Programmiersprache nach C# portiert. Zudem erreicht der Tester mit xUnit eine bessere Performance und höhere Effizienz, da xUnit (wie NUnit ab Version 3) eine parallele Ausführung der Testfälle unterstützt. xUnit unterstützt direkt DI (Dependency Injection), lässt sich leicht in eine CI/CD-Pipeline integrieren und besitzt ein wachsendes Ökosystem für Erweiterungen und Plug-ins.Die Einrichtung der Infrastruktur startet mit dem Ordner, der die zu testende Blazor-App enthält; dort definiert man ein Testprojekt, sodass sich anschließend beide (Blazor-Entwicklungsprojekt und Testprojekt) im selben übergeordneten Projektverzeichnis befinden. Konkret erledigt Visual Studio dies über die Menü-Kaskade File | New Project … und Auswahl des gewünschten Unit-Testing-Frameworks xUnit, das sich in der Template-Gruppe Tests befindet. Bei der Auswahl des Verzeichnisses gilt es darauf zu achten, dass sich Blazor- und Testprojekt im selben Ordner befinden. Visual Studio benötigt in der Solution-Datei eine Referenz auf das neue Testprojekt, dies erledigt der Eintrag Add Existing Project … über die Menü-Kaskade File | Add.Aus Gründen der Kompatibilität zwischen Visual Studio und VS Code empfiehlt sich für beide IDEs der Einsatz einer Solution-Datei (Projektmappe). Dazu legt man in VS Code über die Command-Palette das xUnit-Testprojekt wie folgt an: Im Blazor-Projekt führt die Befehlsfolge .NET: New Project …, Eingabe von xUnit und Auswahl von xUnit Test Project … zu einem neuen Ordner mit einem xUnit-Testprojekt. Anschließend wählt der Tester den Default-Ordner aus, in diesem befindet sich auch das zu testende Blazor-Projekt. Als Ergebnis davon existiert jetzt eine Verknüpfung zwischen dem Blazor- und dem xUnit-Testprojekt in der gemeinsamen Solution-Datei (Projektmappe).Alternativ stellen verschiedene dotnet-Kommandos in einem Blazor-Projekt die Infrastruktur für xUnit-Tests bereit. Zunächst legt der Terminalbefehl mkdir TestProj oberhalb des Ordners mit der Blazor-App ein neues Verzeichnis für das Testprojekt an. Da xUnit in die .NET-Plattform eingebettet ist, steht dessen CLI direkt über das dotnet-Kommando zur Verfügung. So erzeugt dotnet new xunit im gerade neu angelegten Ordner ein Testprojekt mit einem Standard-Testfall für xUnit. Abschließend verknüpft der dotnet-Befehl add reference <PfadZumBlazorProjekt/NameBlazorApp.csproj> im Ordner des xUnit-Testprojekts dessen Projektdatei mit der zugehörigen Blazor-App. Alternativ muss das Testprojekt in einer Solutions-Datei/Projektmappe zusammen mit der Blazor-App eingetragen sein.Arbeiten mit xUnit und dotnet-CLI in Visual Studio und VS Code
Bei Einsatz von Visual Studio / VS Code muss neben dem Paket xunit auch xunit.runner.visualstudio im Testprojekt vorhanden sein: Die Ausgabe von dotnet list package zeigt, ob das Testprojekt beide Pakete enthält. Der beim Einrichten von xUnit generierte Quellcode für die Standard-Testklasse UnitTest1 befindet sich in der Datei UnitTest1.cs mit dem Testfall Test1(). Diesen bringt der Befehl dotnet test über das Testprojekt zur Ausführung; danach erfolgt direkt die Ausgabe der Testergebnisse. Das Hilfesystem für die Ausführung von xUnit-Tests zeigt der Terminalbefehl dotnet test -h an. Alle verfügbaren Testfälle gibt der Befehl dotnet test --list-tests aus.Das Menü View | Tests öffnet in Visual Studio den Test Explorer mit den xUnit-Testfällen – vorausgesetzt, das Testprojekt ist mit der Solution-Datei des Blazor-Projekts verknüpft. Das Kontextmenü des Test Explorer macht in Visual Studio verschiedene Befehle für das Arbeiten mit den xUnit-Testfunktionen zugänglich. Die Ergebnisse der Ausführung von xUnit-Tests zeigt der Fensterausschnitt Test Results an. Dieses Fenster öffnet Visual Studio über View | Other Windows | Test Results. Die Toolleiste von Test Results teilt die Testergebnisse in verschiedene Gruppen ein, die jeweils unterschiedliche Aspekte aufbereiten.Zur Ausführung von Unit-Tests in VS Code empfiehlt sich der Einsatz der Marketplace-Extension .NET Core Test Explorer von Jun Han. Diesen Test Explorer erreicht man in einem Testprojekt über das Menü View | Testing. Zusätzlich erscheint im Panel-Bereich unterhalb des Quellcodes das TEST RESULTS-Register; dort zeigt VS Code die Testergebnisse aller innerhalb einer Session ausgeführten Testfälle an. Bei Problemen macht das Explorer Window von VS Code beim Test-Explorer-Eintrag über das ganz rechts stehende Icon-Symbol die zugehörige Log-Datei zugänglich.Als Alternative steht in VS Code die Marketplace Extension Test Explorer UI von Holger Benl zur Verfügung; diese benötigt für .NET-Testing die Installation des Adapters .Net Core Test Explorer von Derivitec Ltd. Eine schnellere Codierung von xUnit-Testfällen in VS Code unterstützt die Marketplace-Extension XUnit Snippets von Spencer Farley. Über die Visual-Studio-Preferences legt der Add-Befehl neue, zusätzliche Code-Snippets im Bereich Text Editor | Code Snippets an.bUnit für Blazor-Tests einrichten und im Testprojekt konfigurieren
Das Einrichten der Infrastruktur für die Programmierung von bUnit-Tests erfolgt über die Installation des bUnit-Pakets und der notwendigen Referenzen im Testprojekt. Dazu gibt es abhängig von der eingesetzten Entwicklungsumgebung verschiedene Vorgehensweisen:- .NET CLI: Im Testprojekt lädt der dotnet-Befehl add package bunit das Paket in das Testprojekt herunter und trägt die notwendigen Abhängigkeiten in die TestProj.csproj-Datei ein. Das .NET SDK enthält NuGet, sodass der dotnet-Befehl direkt diesen Package-Manager benutzen kann.
- Visual Studio: Nach Auswahl des Testprojekts im Solution Explorer erscheint der Eintrag Manage NuGet Packages … in dessen Kontextmenü. Damit öffnet sich das Fenster NuGet Packages – …(Bild 3), um das bUnit-Paket zu installieren. Das bUnit-Paket enthält bereits die ebenfalls angezeigten Pakete bUnit.core und bUnit.web.

Visual Studio enthält einen integrierten NuGet-Package-Manager, der auf unterschiedliche Repositories zugreifen kann (Bild 3)
Autor
- VS Code: Die Installation von bUnit erfolgt über die Command-Palette mit dem Befehl NuGet Package Manager: Add Package. Nach Eingabe von bUnit und Auswahl der gewünschten Version muss im letzten Dialogschritt die TestProj.csproj-Datei für die Eintragung der Package-Referenzen ausgewählt werden.
bUnit-Testfall am Beispiel der Standard-Blazor-App programmieren
Für den Einstieg in die Programmierung von Testfällen mit bUnit kommt als Beispielprojekt die von dotnet generierte Blazor-App zum Einsatz, die auf dem Template blazorwasm basiert: dotnet new blazorwasm -o BspProj. Zusätzlich wird eine Installation von xUnit und bUnit in einem eigenständigen Testprojekt vorausgesetzt. Der neu im Testprojekt zu codierende Testfall soll prüfen, ob sich der Zähler der Blazor-Komponente Counter.razor des Beispielprojekts durch Klick auf die Click me-Schaltfläche von 0 auf 1 erhöht. Die Basisklasse von bUnit stellt TestContext dar – sie stellt viele grundlegende Funktionen des Testing-Frameworks bereit. Ein Testfall TestCounter.cs macht TestContext entweder durch Vererbung in dessen Klassendeklaration oder durch Instanzierung eines Objekts dieser Klasse im Arrange-Bereich (Listing 1) des Unit-Tests zugänglich.Listing 1: bUnit-Testfall für Standard-Blazor-App
// Die in Pages enthaltenen Blazor-Komponenten <br/>// bereitstellen<br/>using BspProj.Pages;<br/>// Das Unit-Testing-Framework bUnit bereitstellen<br/>using Bunit;<br/>namespace BlazorTests.UnitTests<br/>{<br/> public class CounterTests<br/> {<br/> [Fact]<br/> public void ShouldIncrementOnClick()<br/> {<br/> // Arrange<br/> using var context = new TestContext();<br/> var component = <br/> context.RenderComponent&lt;Counter&gt;();<br/> // Act – Built-in Dispatch-Event-Handler Click <br/> // ausfuehren<br/> component.Find("button").Click();<br/> // Assert<br/> component.Find("p").MarkupMatches(<br/> "&lt;p role=\"status\"&gt;Current count: 1&lt;/p&gt;");<br/> }<br/> }<br/>}
Danach erzeugt RenderComponent<Counter>() die zu testende Blazor-Komponente, kurz CUT (Component under Test) genannt, und macht diese über die component-Variable zugänglich. Die Find()-Funktion von bUnit sucht die Schaltfläche button über component und führt einen Klick mittels der Click()-Funktion aus. Dabei lokalisiert Find() die erste in der Blazor-Komponente enthaltene Schaltfläche – der Klick auf diese bringt die in der Counter-Komponente enthaltene IncrementCount()-Funktion zur Ausführung. Als alternative Auswahlkriterien stehen bei Find() auch id- oder class-Attribute zur Verfügung, um gezielt ein ganz bestimmtes HTML-Element aufzufinden.Der Assert-Bereich sucht über einen weiteren Find()-Aufruf nach dem ersten Auftreten eines Paragrafen/Absatzes. Anschließend prüft der zweite nach dem Find()-Aufruf folgende Schritt, ob der gefundene <p>-Absatz die an MarkupMatches() übergebene Zeichenkette enthält (Listing 1). Beim Rendern einer Blazor-Komponente erzeugt bUnit ein IRenderedFragment-Objekt und macht darüber das gerenderte Markup (HTML) zugänglich. Für die Überprüfung von erzeugtem HTML (dem Output) steht stets der Gedanke der Programmierung stabiler Tests im Zentrum, um Flaky-Tests (siehe [5]) möglichst auszuschließen. Für einen HTML-Vergleich unterstützt bUnit vier verschiedene Ansätze:
- Raw: bUnit übernimmt aus der Blazor-Komponente das betroffene HTML-Fragment als reine Zeichenkette. Vergleiche auf der Ebene reiner Zeichenketten können zu Problemen führen, falls die Blazor-Komponente Leerzeichen und Einrückungen zurückliefert.
- Semantischer Vergleich: Führt in der Regel zu stabileren Tests, da der Vergleich nicht Zeichen für Zeichen, sondern auf einer höheren Ebene stattfindet. bUnit benutzt dazu die Diffing-Library AngleSharp. Optionen beim Vergleich steuern dessen Durchführung, was ein Customizing ermöglicht.
- Vergleich individueller DOM-Knoten: Die Nodes-Property von IRenderedFragment liefert eine INodeList-Liste oder ein IElement von AngleSharp zurück. Anschließende Vergleiche finden mit dem W3C-kompatiblen DOM-API von AngleSharp statt.
- Auffinden erwarteter Unterschiede: Anstatt auf einer konkreten Rückgabe basiert dieser Vergleich auf einem Unterschied, der zu erwarten ist. Für diesen Vergleich ermittelt bUnit in IDiff die aufgetretenen Unterschiede der beiden HTML-Fragmente.
Besondere Features von bUnit für das Testing einer Blazor-Komponente
Die Programmierung eines Komponententests für eine CUT (Component under Test) erfolgt über .razor- oder .cs-Dateien. Dabei kommuniziert bUnit mit der CUT über bestimmte im Testing-Framework realisierte Features; diese lassen sich in folgende Klassen einteilen: Output (bereits oben behandelt), Input, Interaktionen und Mocking/Faking. Beim Input an eine CUT handelt es sich um die Übergabe von Parametern. Realisiert der Programmierer seine Tests in einer .razor-Datei, so findet der Input als Inline-Razor-Template über die Render()-Methode statt. Im Fall einer .cs-Datei greift der Programmierer auf die beiden bUnit-Methoden RenderComponent und SetParametersAndRender zurück.Zum Testen des Inputs an eine CUT gehören die Dependency Injection (DI), die Anpassung des Wurzel-Render-Baums und die Substitution einer Komponente. Das Injecting von Services erfolgt in einer .razor-Datei über die @inject-Anweisung. Um deren korrekte Durchführung für die CUT zu prüfen, fügt der Tester den betroffenen Service einer bUnit-Services-Collection mittels der AddSingleton-Funktion von .NET-Core-DI hinzu. Nach dem Rendering der Blazor-Komponente über das bUnit-Testing-Framework steht der Service in der Instanz der CUT zur Verfügung.Die Anpassung eines Wurzel-Render-Baums bezweckt, dass allen CUTs unterhalb des Einfügepunkts die neu aufgenommene Funktionalität zugänglich wird. Dazu fügt die Funktion CascadingValue den Knoten mit der Funktionalität dem Baum hinzu und PrintCascadingValue rendert die Komponente. Die Substitution der Kind-Komponente einer CUT erzeugt einen Platzhalter, um die CUT von anderen Komponenten zu isolieren. Eine Substitution ermöglicht die Einbindung externer Komponenten von Drittherstellern. Für die Realisierung greift der Tester auf ComponentFactories einer bUnit-Collection zurück.Interaktionen mit einer CUT fokussieren auf die Einbindung von Eventhandlern. Alle über die Elemente einer CUT verbundenen Eventhandler besitzen in bUnit ein passendes Pendant. Dabei handelt es sich um Built-in-Dispatch-Event-Helper von bUnit, die der Tester direkt über das zugehörige Element anspricht.Zusätzlich stehen in bUnit Methoden zur Verfügung, um im Rahmen des Lifecycle einer Komponente ein Rendering durchzuführen. Findet das Rendering asynchron statt, so muss mittels der beiden bUnit-Methoden WaitForState oder WaitForAssertion auf Zustandsänderungen gewartet werden. Verwendet die CUT spezielle Freigabe/Disposing-Mechanismen, so lassen sich diese mithilfe der DisposeComponents-Methode testen.Testen der Dependency Injection mit bUnit für die Standard-Blazor-App
Die mittels dotnet new blazorwasm -o BspProj erzeugte Standard-Blazor-App enthält im Ordner pages die Blazor-Komponente FetchData.razor. Sie soll durch eine Komponente ersetzt werden, die lediglich eine durch einen Service ausgegebene Textnachricht anzeigt. Der Quellcode des Service befindet sich in einer eigenständigen Klasse MeinService.cs(Listing 2). Sobald der Endbenutzer in der linken Navigationsleiste der Blazor-App BspProj auf den dortigen Menüeintrag Fetch data klickt, gibt der Service die Textnachricht aus (Bild 4). Diesen Service erhält die FetchData-Komponente der Blazor-App durch Dependency Injection (DI) hinzugefügt (Listing 3).Listing 2: Klasse MeinService mit Textnachricht
public class MeinService<br/>{<br/> public string GetMessage()<br/> {<br/> return "Guten Tag sendet MeinService!";<br/> }<br/>}

Ein Klick auf den Menüeintrag Fetch data in der linken Navigationsleiste führt den durch DI hinzugefügten Service der Blazor-App aus (Bild 4)
Autor
Listing 3: Geänderte Blazor-Komponente FetchData
@page "/fetchdata"<br/>@inject MeinService myService<br/>&lt;h3&gt;MyComponent&lt;/h3&gt;<br/>&lt;p&gt;@message&lt;/p&gt;<br/>@code {<br/> private string message;<br/> protected override void OnInitialized()<br/> {<br/> message = myService.GetMessage();<br/> }<br/>}
Damit zur Ausführungszeit der Blazor-App BspProj der Service bereitsteht, muss dieser in deren Startup-Datei Program.cs über einen neuen Eintrag builder.Services.AddScoped
<MeinService>(); aufgenommen werden. Diese Zeile muss vor dem Start der Blazor-App mittels await builder.Build().
RunAsync(); stehen. Erst danach erscheint die Textnachricht des neuen Service in der Blazor-App BspProj(Bild 4). Ein Komponententest mit bUnit soll prüfen, ob der Service MeinService mittels DI der Blazor-App korrekt hinzugefügt wird. Dazu kommt die Anweisung @inject MeinService myService im Quelltext der FetchData-Komponente zum Einsatz. Der Zugriff, das heißt die Ausgabe der Textnachricht, erfolgt durch Ausführung der Methode GetMessage() über die Klasse MeinService.cs(Listing 2).Für den Komponententest kommt ein xUnit-Testprojekt TestProj zum Einsatz. Dort programmiert man eine Klasse MyComponentTests.cs(Listing 4), um die korrekte Ausführung des durch DI hinzugefügten Service zu prüfen. Zu Beginn benötigt bUnit einen TestContext, diesem fügt die Methode AddScoped() den zu testenden Service hinzu. Danach führt RenderComponent das Rendering der FetchData-Komponente der Blazor-App aus. Die FetchData-Komponente erzeugt über die Anweisung <p>@message</p> im Quellcode (Listing 3) einen Absatz, der die Textnachricht der MeinService-Klasse aufnimmt.
<MeinService>(); aufgenommen werden. Diese Zeile muss vor dem Start der Blazor-App mittels await builder.Build().
RunAsync(); stehen. Erst danach erscheint die Textnachricht des neuen Service in der Blazor-App BspProj(Bild 4). Ein Komponententest mit bUnit soll prüfen, ob der Service MeinService mittels DI der Blazor-App korrekt hinzugefügt wird. Dazu kommt die Anweisung @inject MeinService myService im Quelltext der FetchData-Komponente zum Einsatz. Der Zugriff, das heißt die Ausgabe der Textnachricht, erfolgt durch Ausführung der Methode GetMessage() über die Klasse MeinService.cs(Listing 2).Für den Komponententest kommt ein xUnit-Testprojekt TestProj zum Einsatz. Dort programmiert man eine Klasse MyComponentTests.cs(Listing 4), um die korrekte Ausführung des durch DI hinzugefügten Service zu prüfen. Zu Beginn benötigt bUnit einen TestContext, diesem fügt die Methode AddScoped() den zu testenden Service hinzu. Danach führt RenderComponent das Rendering der FetchData-Komponente der Blazor-App aus. Die FetchData-Komponente erzeugt über die Anweisung <p>@message</p> im Quellcode (Listing 3) einen Absatz, der die Textnachricht der MeinService-Klasse aufnimmt.
Listing 4: Die bUnit-Testklasse MyComponentTests
namespace TestProj;<br/>using Bunit;<br/>using Microsoft.Extensions.DependencyInjection;<br/>using BspProj.Pages;<br/>public class MyComponentTests<br/>{<br/> [Fact]<br/> public void MyComponentShouldInjectMyService()<br/> {<br/> // Arrange<br/> using var context = new TestContext();<br/> <br/> // Den Service dem TestContext hinzufuegen<br/> context.Services.AddScoped&lt;MeinService&gt;();<br/> // Act<br/> var cut = context.RenderComponent&lt;FetchData&gt;();<br/> // Assert<br/> cut.Find("p").MarkupMatches(<br/> "&lt;p&gt;Guten Tag sendet MeinService!&lt;/p&gt;");<br/> }<br/>}
Diesen durch das <p>-Tag erzeugten Absatz/Paragrafen sucht die Anweisung cut.Find(”p”) von bUnit in der Testklasse MyComponentTests. Wenn die Find()-Methode über die von bUnit gerenderte Blazor-Komponente das <p>-Tag lokalisiert, kommt abschließend die MarkupMatches()-Methode (Listing 4) zur Ausführung. Diese überprüft, ob der durch Dependency Injection hinzugefügte Service die gewünschte Textnachricht in der gerenderten Blazor-Komponente erzeugt hat.Der Befehl dotnet test --filter ”FullyQualifiedName=TestProj.MyComponentTests.MyComponentShouldInjectMyService” startet über xUnit im Testprojekt den bUnit-Testfall und führt ihn aus. Im Erfolgsfall erscheint dieser Testfall als Passed in der Ausgabeliste von xUnit.
Fussnoten
- Homepage von .NET, http://www.dotnetpro.de/SL2401Playwright1
- Homepage von Blazor, http://www.dotnetpro.de/SL2401Playwright2
- Razor-Markup-Syntax, http://www.dotnetpro.de/SL2401Playwright3
- Homepage von bUnit, http://www.dotnetpro.de/SL2401Playwright4
- Frank Simon, Running, Debugging und Analyse, dotnetpro 11/2023, Seite 118 ff., http://www.dotnetpro.de/A2311Playwright