Mock-Frameworks in C# verwenden (Moq, NSubstitute)
Lernen Sie Mock-Frameworks wie Moq und NSubstitute in C#, um Abhängigkeiten in Unit-Tests zu isolieren.
In realen Unit-Tests muss das Verhalten der zu testenden Klasse von ihren Abhängigkeiten isoliert werden.
Genau hier kommen Mock-Frameworks ins Spiel.
Beliebte Bibliotheken wie Moq und NSubstitute erstellen Scheinobjekte (Mock-Objekte),
um die Abhängigkeiten zu simulieren.
So wird ausschließlich das Verhalten der getesteten Klasse überprüft.
Was ist ein Mock?
Ein Mock ist ein Scheinobjekt, das reale Abhängigkeiten einer Klasse ersetzt. Dadurch kann während der Tests:
- Auf reale Datenbank- oder Serviceverbindungen verzichtet werden,
- Tests laufen schneller und isolierter,
- Fehler durch externe Einflüsse (API, Netzwerk, Festplatte) werden vermieden,
- Überprüft werden, ob bestimmte Methodenaufrufe erfolgt sind.
// Beispielszenario: Benutzerregistrierung mit E-Mail-Service-Abhängigkeit
public interface IEmailDienst
{
void Senden(string adresse, string nachricht);
}
public class BenutzerRegistrierung
{
private readonly IEmailDienst _emailDienst;
public BenutzerRegistrierung(IEmailDienst emailDienst)
{
_emailDienst = emailDienst;
}
public void Registrieren(string name)
{
// Registrierungsvorgang...
_emailDienst.Senden("admin@site.com", $"{name} wurde erfolgreich registriert!");
}
}
Beim Testen dieser Klasse möchten wir keine echte E-Mail versenden. Stattdessen verwenden wir einen Mock-E-Mail-Service.
Was ist das Moq-Framework?
Moq ist eines der beliebtesten Mock-Frameworks im .NET-Ökosystem. Es verwendet eine Fluent API, ist leicht lesbar und bietet eine leistungsstarke Verifikationsunterstützung.
// Installation über NuGet:
dotnet add package Moq
using Moq;
using Xunit;
public class BenutzerRegistrierungTests
{
[Fact]
public void Registrieren_SollteEmailSenden()
{
// Arrange
var mockMail = new Mock<IEmailDienst>();
var registrierung = new BenutzerRegistrierung(mockMail.Object);
// Act
registrierung.Registrieren("Ahmet");
// Assert
mockMail.Verify(
m => m.Senden("admin@site.com", "Ahmet wurde erfolgreich registriert!"),
Times.Once); // Methode sollte genau einmal aufgerufen werden
}
}
Die Verify()-Methode prüft, ob ein bestimmtes Verhalten tatsächlich aufgetreten ist.
Damit wird ein verhaltensbasierter Test (Behavior Verification) ermöglicht.
Mock-Verhalten festlegen
Mit Moq kann man nicht nur Verifikationen durchführen, sondern auch Rückgabewerte für Scheinmethoden definieren.
public interface ILagerDienst
{
int Holen(string produktCode);
}
public class ProduktVerkauf
{
private readonly ILagerDienst _lager;
public ProduktVerkauf(ILagerDienst lager) => _lager = lager;
public bool Verkaufen(string produktCode)
{
int menge = _lager.Holen(produktCode);
return menge > 0;
}
}
public class ProduktVerkaufTests
{
[Fact]
public void WennImLager_VerkaufSollteErfolgreichSein()
{
var mock = new Mock<ILagerDienst>();
mock.Setup(s => s.Holen("ABC123")).Returns(10);
var verkauf = new ProduktVerkauf(mock.Object);
bool ergebnis = verkauf.Verkaufen("ABC123");
Assert.True(ergebnis);
}
}
Die Setup()-Methode legt fest, wie sich das Mock-Objekt bei bestimmten Aufrufen verhalten soll.
Mit Returns() wird der Rückgabewert definiert.
Callback-Verwendung
In Moq wird die Callback()-Methode verwendet, um beim Methodenaufruf Nebenwirkungen (Side Effects) auszulösen –
z. B. Protokollierung oder Erhöhung eines Zählers während des Tests.
int zaehler = 0;
var mock = new Mock<IEmailDienst>();
mock.Setup(m => m.Senden(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => zaehler++);
var registrierung = new BenutzerRegistrierung(mock.Object);
registrierung.Registrieren("Zeynep");
Assert.Equal(1, zaehler);
In diesem Beispiel wird der Zähler jedes Mal erhöht, wenn Senden() aufgerufen wird.
Diese Methode ist sehr nützlich, um Ereignisse in Tests zu verfolgen.
Die It-Hilfsklasse
Moq stellt die It-Hilfsklasse für Parameterüberprüfungen bereit. Damit können Sie nur bestimmte Parameter prüfen oder Platzhalter verwenden.
It.IsAny<T>()– Akzeptiert jeden Wert.It.Is<T>(predicate)– Passt auf Werte, die eine Bedingung erfüllen.It.IsInRange(min, max, Range.Inclusive)– Testet Wertebereiche.
mock.Verify(m =>
m.Senden(It.IsAny<string>(),
It.Is<string>(msg => msg.Contains("Ahmet"))),
Times.Once);
Was ist das NSubstitute-Framework?
NSubstitute bietet eine einfache API zur Erstellung von Mocks, ähnlich wie Moq. Es zeichnet sich durch hohe Lesbarkeit aus und unterstützt das „Arrange–Act–Assert“-Muster auf natürliche Weise.
// Installation über NuGet:
dotnet add package NSubstitute
using NSubstitute;
using Xunit;
public class BenutzerRegistrierungTests
{
[Fact]
public void Registrieren_SollteEmailSenden()
{
var email = Substitute.For<IEmailService>();
var registrierung = new BenutzerRegistrierung(email);
registrierung.Registrieren("Mehmet");
email.Received(1).Senden("admin@site.com", "Mehmet wurde erfolgreich registriert!");
}
}
Die Methode Received() überprüft, ob ein Methodenaufruf tatsächlich stattgefunden hat.
Sie bietet eine natürlichere und besser lesbare Syntax für Tests.
Verhalten definieren (Returns und When)
In NSubstitute können Mock-Verhalten mit den Methoden Returns() und When() definiert werden.
var lager = Substitute.For<ILagerService>();
lager.Hole("P001").Returns(5);
var verkauf = new ProduktVerkauf(lager);
bool ergebnis = verkauf.Verkaufe("P001");
Assert.True(ergebnis);
// Nebeneffekt während des Aufrufs hinzufügen:
lager.When(l => l.Hole("P001"))
.Do(_ => Console.WriteLine("Lagerservice wurde aufgerufen."));
Die Struktur When(...).Do(...) ist ideal, um benutzerdefinierte Aktionen auszuführen, wenn ein bestimmter Aufruf erfolgt.
Vergleich: Moq vs NSubstitute
| Eigenschaft | Moq | NSubstitute |
|---|---|---|
| Verwendungsstil | Fluent API (Setup/Verify) | Natürliche Sprache (Returns/Received) |
| Lesbarkeit | Hoch | Sehr hoch |
| Leistung | Etwas schneller | Durchschnittlich |
| Callback-Unterstützung | Ja (.Callback()) | Ja (.Do()) |
| Popularität | Sehr verbreitet (häufig mit xUnit) | Weniger verbreitet, aber moderne Syntax |
Praxisbeispiel: Test des Bestellservices
Das folgende Beispiel zeigt ein Szenario, in dem eine Servicedependenz sowohl mit Moq als auch mit NSubstitute getestet wird.
public interface IBestellService
{
void Speichern(string produkt);
}
public class EMailService
{
public virtual void Senden(string nachricht)
{
Console.WriteLine($"E-Mail gesendet: {nachricht}");
}
}
public class BestellVorgang
{
private readonly IBestellService _bestell;
private readonly EMailService _mail;
public BestellVorgang(IBestellService bestell, EMailService mail)
{
_bestell = bestell;
_mail = mail;
}
public void Abschluss(string produkt)
{
_bestell.Speichern(produkt);
_mail.Senden($"{produkt} Bestellung abgeschlossen!");
}
}
// Test mit Moq
var bestellMock = new Mock<IBestellService>();
var mailMock = new Mock<EMailService>();
var vorgang = new BestellVorgang(bestellMock.Object, mailMock.Object);
vorgang.Abschluss("Laptop");
bestellMock.Verify(b => b.Speichern("Laptop"), Times.Once);
mailMock.Verify(m => m.Senden("Laptop Bestellung abgeschlossen!"), Times.Once);
// Test mit NSubstitute
var bestell = Substitute.For<IBestellService>();
var mail = Substitute.For<EMailService>();
var vorgang2 = new BestellVorgang(bestell, mail);
vorgang2.Abschluss("Laptop");
bestell.Received(1).Speichern("Laptop");
mail.Received(1).Senden("Laptop Bestellung abgeschlossen!");
Leistung und Best Practices
- Verwenden Sie Mocks nur für externe Abhängigkeiten (Datenbank, Service, API, Dateizugriff usw.).
- Verwenden Sie bei der Prüfung der Geschäftslogik reale Klassen; Mocks sollten nur externe Services darstellen.
- Vermeiden Sie unnötige Verifikationen – Tests sollten einfach und klar bleiben.
- Befolgen Sie in Unit-Tests das Muster
Arrange – Act – Assert. - Erkennen Sie, wann Fakes oder Stubs anstelle von Mocks verwendet werden sollten.
TL;DR
- Mock-Frameworks erstellen Fake-Objekte, um Abhängigkeiten in Tests zu isolieren.
- Moq: Fluent-API-Stil, weit verbreitet mit starker Verifikationsunterstützung.
- NSubstitute: Bietet eine natürlichere und lesbarere Syntax.
Setup()undReturns()→ Definieren Verhalten.Verify()/Received()→ Überprüfen Aufrufe.- Mocks konzentrieren sich darauf, die Zielklasse zu testen, ohne mit echten Systemen zu interagieren.
Ähnliche Artikel
Delegates und Ereignisse in C#
Lernen Sie Delegates und Events in C#, um ereignisgesteuerte Programmierung mit Callbacks und Beispielen umzusetzen.
Dependency Injection Grundlagen in C#
Lernen Sie die Grundlagen von Dependency Injection in C#, um Abhängigkeiten zu verwalten und lose Kopplung zu erreichen.
Schreiben von Unit-Tests in C# (xUnit, NUnit, MSTest)
Lernen Sie Unit-Tests in C# mit xUnit, NUnit und MSTest zu schreiben, um zuverlässige Software zu entwickeln.