Utilisation des frameworks de mock en C# (Moq, NSubstitute)
Apprenez à utiliser des frameworks de mock en C# comme Moq et NSubstitute pour isoler les dépendances en tests unitaires.
Dans les tests unitaires réels, il est essentiel d’isoler le comportement de la classe testée de ses dépendances.
C’est là qu’interviennent les frameworks de mock.
Des bibliothèques populaires comme Moq et NSubstitute permettent de créer des objets factices (mock objects)
pour simuler les dépendances.
Ainsi, seul le comportement de la classe testée est vérifié.
Qu’est-ce qu’un Mock ?
Un mock est un objet factice utilisé à la place des dépendances réelles d’une classe. Cela permet, lors des tests :
- D’éviter les connexions réelles à une base de données ou à un service,
- De rendre les tests plus rapides et isolés,
- D’éviter les erreurs dues à des facteurs externes (API, réseau, disque),
- De vérifier si certains appels de méthode ont bien été effectués.
// Exemple de scénario : un service d'inscription utilisateur dépendant d’un service d’e-mail
public interface IEmailService
{
void Envoyer(string adresse, string message);
}
public class InscriptionUtilisateur
{
private readonly IEmailService _emailService;
public InscriptionUtilisateur(IEmailService emailService)
{
_emailService = emailService;
}
public void Inscrire(string nom)
{
// Processus d’inscription...
_emailService.Envoyer("admin@site.com", $"{nom} a été enregistré avec succès !");
}
}
Lors du test de cette classe, nous ne voulons pas envoyer un vrai e-mail. Dans ce cas, nous utilisons un service e-mail simulé.
Qu’est-ce que le framework Moq ?
Moq est l’un des frameworks de mock les plus populaires de l’écosystème .NET. Il utilise une API fluide (Fluent API), lisible et fournit un puissant support de vérification (verification).
// Installation via NuGet :
dotnet add package Moq
using Moq;
using Xunit;
public class InscriptionUtilisateurTests
{
[Fact]
public void Inscrire_DoîtEnvoyerEmail()
{
// Arrange
var mockMail = new Mock<IEmailService>();
var inscription = new InscriptionUtilisateur(mockMail.Object);
// Act
inscription.Inscrire("Ahmet");
// Assert
mockMail.Verify(
m => m.Envoyer("admin@site.com", "Ahmet a été enregistré avec succès !"),
Times.Once); // La méthode doit être appelée une fois
}
}
La méthode Verify() permet de vérifier qu’un comportement particulier s’est bien produit.
Cela permet de réaliser des tests basés sur le comportement (behavior verification).
Définir le comportement des Mocks
Avec Moq, il est possible non seulement de vérifier les comportements, mais aussi de définir les valeurs de retour des méthodes simulées.
public interface IStockService
{
int Obtenir(string codeProduit);
}
public class VenteProduit
{
private readonly IStockService _stock;
public VenteProduit(IStockService stock) => _stock = stock;
public bool Vendre(string codeProduit)
{
int quantite = _stock.Obtenir(codeProduit);
return quantite > 0;
}
}
public class VenteProduitTests
{
[Fact]
public void SiStockDisponible_VenteReussie()
{
var mock = new Mock<IStockService>();
mock.Setup(s => s.Obtenir("ABC123")).Returns(10);
var vente = new VenteProduit(mock.Object);
bool resultat = vente.Vendre("ABC123");
Assert.True(resultat);
}
}
La méthode Setup() définit comment l’objet simulé doit se comporter pour un appel donné.
Returns() précise la valeur à retourner.
Utilisation de Callback
Dans Moq, la méthode Callback() permet d’ajouter un effet secondaire lorsqu’une méthode est appelée —
par exemple pour écrire un journal ou incrémenter un compteur pendant le test.
int compteur = 0;
var mock = new Mock<IEmailService>();
mock.Setup(m => m.Envoyer(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => compteur++);
var inscription = new InscriptionUtilisateur(mock.Object);
inscription.Inscrire("Zeynep");
Assert.Equal(1, compteur);
Dans cet exemple, le compteur s’incrémente chaque fois que Envoyer() est appelé.
Cette technique est très utile pour le suivi des événements dans les tests.
La classe utilitaire It
Moq fournit la classe It pour effectuer des vérifications de paramètres. Elle permet de tester uniquement certains paramètres ou d’utiliser des jokers.
It.IsAny<T>()– Accepte n’importe quelle valeur.It.Is<T>(predicate)– Correspond aux valeurs satisfaisant une condition.It.IsInRange(min, max, Range.Inclusive)– Vérifie les plages de valeurs.
mock.Verify(m =>
m.Envoyer(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.
Articles connexes
Délégués et Événements en C#
Apprenez les délégués et événements en C# pour comprendre la programmation événementielle avec des exemples concrets.
Écriture de tests unitaires en C# (xUnit, NUnit, MSTest)
Apprenez à écrire des tests unitaires en C# avec xUnit, NUnit et MSTest pour améliorer la qualité du code.
Principes de l’Injection de Dépendances en C#
Apprenez les principes de l’Injection de Dépendances en C#, la gestion des dépendances et le couplage faible avec exemples.