Unit-Tests in C#: Mocking-Strategie für testbare .NET-Anwendungen

Isolierte Unit-Tests in .NET
Ömer K.
02/2026

Mocking-Strategie für testbare .NET-Anwendungen

Die Entwicklung moderner .NET-Anwendungen erfordert eine saubere und verlässliche Testbasis. Besonders in Systemen mit komplexer Geschäftslogik kann bereits eine kleine Codeänderung unerwartete Auswirkungen auf verschiedene Verarbeitungsschritte haben. Unit-Tests sorgen dafür, dass einzelne Komponenten isoliert geprüft und deren Verhalten unabhängig von Datenbank, Services oder externen Abhängigkeiten reproduzierbar bleibt.

Durch gezielten Einsatz von Mocking lassen sich selbst umfangreiche Berechnungsabläufe stabil testen. Fehlerquellen werden frühzeitig sichtbar, künftige Erweiterungen werden einfacher und sicherer umsetzbar. Dieser Beitrag zeigt, wie eine strukturierte Testarchitektur entsteht und wie sich Kernlogik zuverlässig automatisieren lässt.

Problemstellung

In vielen bestehenden .NET-Anwendungen ist die Geschäftslogik stark mit externen Abhängigkeiten verbunden – etwa Datenbanken, Webservices oder fest codierte Konfigurationen. Dies erschwert die Umsetzung automatisierter Unit-Tests erheblich.

Insbesondere in Legacy-Code führt jede Änderung potenziell zu unbeabsichtigten Nebeneffekten. Solche Codebasen werden daher häufig gar nicht oder nur unzureichend getestet. Ein typisches Beispiel ist die Abhängigkeit von externen Services bei Berechnungen: Deren Ergebnisse lassen sich im Test nicht zuverlässig kontrollieren. Ohne klare Schnittstellen ist eine Trennung oder Simulation dieser Abhängigkeiten kaum möglich. Das resultiert in instabilen Tests oder ungetesteter Kernlogik.

Lösungsansatz

Um Geschäftslogik effektiv testen zu können, müssen externe Abhängigkeiten konsequent entkoppelt werden. Ziel ist es, Berechnungslogik, Datenzugriffe und externe Services so zu kapseln, dass jede Komponente isoliert getestet werden kann. Dies gelingt durch den Einsatz klar definierter Interfaces in Kombination mit Dependency Injection.

Durch die Einführung schlanker Schnittstellen lassen sich externe Services gezielt mocken und kontrollierte Testergebnisse erzeugen. Bestehender Code muss dabei nur minimal angepasst werden – typischerweise auf Konstruktoren und Abhängigkeiten beschränkt. Das Ergebnis ist eine flexible Testarchitektur, die automatisierte Tests unterstützt und die Wartbarkeit des Systems nachhaltig verbessert.

Teststrategie und Umsetzung

Die zugrundeliegende Teststrategie basiert auf der vollständigen Isolation der Geschäftslogik. Externe Abhängigkeiten wie Datenbankzugriffe oder Webservice-Aufrufe werden nicht direkt aufgerufen, sondern über Interfaces abstrahiert. In Unit-Tests kommen Mocks zum Einsatz, um das Verhalten dieser Abhängigkeiten gezielt zu steuern und reproduzierbare Ergebnisse zu erzielen.

Für jeden relevanten Verarbeitungsschritt werden dedizierte Tests definiert. Dabei wird nicht nur geprüft, ob Methoden erfolgreich durchlaufen, sondern auch, ob fachliche Seiteneffekte korrekt ausgelöst werden – beispielsweise das Erzeugen von Datenbankeinträgen oder das Setzen von Statuswerten. Mithilfe von Callback-Mechanismen in den Mocks lassen sich diese Effekte verlässlich verifizieren, ohne auf eine echte Datenbank angewiesen zu sein.

Ein besonderes Augenmerk liegt auf der Nachvollziehbarkeit der Tests. Die Testdaten werden über strukturierte Hilfsklassen bereitgestellt, sodass sich Testszenarien gezielt erweitern und anpassen lassen. So entsteht eine wartbare Testbasis, die auch bei wachsender Komplexität stabil bleibt.

Praxisbeispiel: Testbare Geschäftslogik mit Mocking in .NET

Das folgende Beispiel zeigt eine typische Situation: Eine Geschäftslogik-Klasse berechnet eine Rechnungssumme auf Basis eines externen Wechselkurs-Services. Für Unit-Tests wird der Service über ein Interface abstrahiert und per Mock ersetzt. Zudem wird ein Repository-Mock verwendet, um Seiteneffekte wie das Speichern zu verifizieren.

1using System; 
2namespace Demo.TestableBusinessLogic 
3{ 
4    public interface IExchangeRateService 
5    { 
6        decimal GetRate(string fromCurrency, string toCurrency); 
7    } 
8    public interface IInvoiceRepository 
9    { 
10        void Save(Invoice invoice); 
11    } 
12    public sealed class Invoice 
13    { 
14        public string InvoiceNo { get; } 
15        public decimal Amount { get; } 
16        public string Currency { get; } 
17        public Invoice(string invoiceNo, decimal amount, string currency) 
18        { 
19
20            InvoiceNo = invoiceNo; 
21            Amount = amount; 
22            Currency = currency; 
23        } 
24    } 
25
26    public sealed class InvoiceResult 
27    { 
28        public decimal TotalEur { get; } 
29        public bool Saved { get; } 
30        public InvoiceResult(decimal totalEur, bool saved) 
31        { 
32            TotalEur = totalEur; 
33            Saved = saved; 
34        } 
35    } 
36
37// Kapselt die Berechnungslogik und delegiert externe Abhängigkeiten an Interfaces. 
38
39    public sealed class InvoiceCalculator 
40    { 
41        private readonly IExchangeRateService exchangeRateService; 
42        private readonly IInvoiceRepository invoiceRepository; 
43 public InvoiceCalculator(IExchangeRateService exchangeRateService, IInvoiceRepository invoiceRepository) 
44        { 
45            this.exchangeRateService = exchangeRateService; 
46            this.invoiceRepository = invoiceRepository; 
47        } 
48
49        // Berechnet den Rechnungsbetrag in EUR und speichert die Rechnung. 
50
51        public InvoiceResult CalculateAndStore(Invoice invoice) 
52        { 
53            if (invoice == null) 
54                throw new ArgumentNullException(nameof(invoice)); 
55            var rate = exchangeRateService.GetRate(invoice.Currency, "EUR"); 
56            var totalEur = Math.Round(invoice.Amount * rate, 2, MidpointRounding.AwayFromZero); 
57            invoiceRepository.Save(invoice); 
58            return new InvoiceResult(totalEur, true); 
59        } 
60    } 
61} 
62
63using Demo.TestableBusinessLogic; 
64using Microsoft.VisualStudio.TestTools.UnitTesting; 
65using Moq; 
66
67namespace Demo.Tests 
68
69{ 
70    [TestClass] 
71    public class InvoiceCalculatorTests 
72    { 
73        // Prüft die korrekte Umrechnung sowie das Auslösen des Speichervorgangs. 
74        [TestMethod] 
75        public void CalculateAndStore_ConvertsAmountAndSavesInvoice() 
76        { 
77            var exchangeRateServiceMock = new Mock<IExchangeRateService>(); 
78            exchangeRateServiceMock 
79                .Setup(x => x.GetRate("USD", "EUR")) 
80                .Returns(0.90m); 
81
82            Invoice savedInvoice = null; 
83            var invoiceRepositoryMock = new Mock<IInvoiceRepository>(); 
84            invoiceRepositoryMock 
85
86                .Setup(x => x.Save(It.IsAny<Invoice>())) 
87                .Callback<Invoice>(i => savedInvoice = i); 
88
89            var sut = new InvoiceCalculator( 
90                exchangeRateServiceMock.Object, 
91                invoiceRepositoryMock.Object); 
92
93            var invoice = new Invoice("INV-1001", 100m, "USD"); 
94            var result = sut.CalculateAndStore(invoice); 
95
96            Assert.AreEqual(90.00m, result.TotalEur); 
97            Assert.IsTrue(result.Saved); 
98            Assert.IsNotNull(savedInvoice); 
99            
100            exchangeRateServiceMock.Verify(x => x.GetRate("USD", "EUR"), Times.Once); 
101            invoiceRepositoryMock.Verify(x => x.Save(It.IsAny<Invoice>()), Times.Once); 
102        } 
103    } 
104}

Ergebnis

Durch den Einsatz von Interfaces und gezieltem Mocking lässt sich Geschäftslogik vollständig isoliert testen. Externe Abhängigkeiten werden kontrollierbar, Testergebnisse bleiben reproduzierbar, und fachliche Seiteneffekte können präzise verifiziert werden. So entsteht eine robuste Testbasis, die Refactorings erleichtert und das Risiko unbeabsichtigter Nebenwirkungen deutlich reduziert.

No items found.
Foto von Ömer
Ömer K.

Mehr zum Thema

Pfeil nach rechts (Verlinkung)
API Architektur mit .NET
02/2026

API-Architektur mit .NET

Blauer Pfeil nach rechts (Verlinkung)
02/2026

C# 14 Sprachfeatures: Extension Members, field-Keyword & mehr (.NET 10)

Blauer Pfeil nach rechts (Verlinkung)
Effiziente nutzung des ORM Frameworks
02/2026

EF Core best practices

Blauer Pfeil nach rechts (Verlinkung)
Business-Central-Webservice – Authentifizierung in der Praxis (C#)
12/2025

Business-Central-Webservice – Authentifizierung in der Praxis (C#)

Blauer Pfeil nach rechts (Verlinkung)

Devware GmbH verpflichtet sich, Ihre Privatsphäre zu schützen. Wir benötigen Ihre Kontaktinformationen, um Sie bezüglich unserer Produkte und Dienstleistungen zu kontaktieren. Mit Klick auf Absenden geben Sie sich damit einverstanden. Weitere Informationen finden Sie unter Datenschutz. Ihre Daten behandeln wir vertraulich. Versprochen.
Vielen Dank für Ihr Vertrauen.
Unser Team prüft Ihre Anfrage sorgfältig und meldet sich in der Regel innerhalb von 48 Stunden bei Ihnen zurück.
Falls es besonders eilig ist, erreichen Sie uns auch telefonisch:
+ 49 (0) 202 478 269 0.
Da ist etwas schief gegangen beim Absenden des Formulars.