Chargement...

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 :


// 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.


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

EigenschaftMoqNSubstitute
VerwendungsstilFluent API (Setup/Verify)Natürliche Sprache (Returns/Received)
LesbarkeitHochSehr hoch
LeistungEtwas schnellerDurchschnittlich
Callback-UnterstützungJa (.Callback())Ja (.Do())
PopularitätSehr 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


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() und Returns() → Definieren Verhalten.
  • Verify() / Received() → Überprüfen Aufrufe.
  • Mocks konzentrieren sich darauf, die Zielklasse zu testen, ohne mit echten Systemen zu interagieren.

Articles connexes