Zum Inhalt

Leichter testen mit Moq

Moq ist ein kleines und sehr einfaches Mock-Framework für .Net / C#. Obwohl es solche Frameworks wie Sand am Meer gibt, so sticht Moq doch heraus. Allzu oft ist das Aufsetzen eines Mocks sehr umständlich und der Test Code wird unnötig kompliziert. Moq dagegen nutzt eine sehr klare Syntax und verfügt über ein praxisorientiertes Standardverhalten.

Wozu Mocks?

Bei Unit-Tests möchte man möglichst nur eine kleine Einheit testen. Meistens aber gibt es Abhängigkeiten zu anderen Einheiten die man für diesen Test ignorieren möchte. Ist man am Erstellen eines Rabattsystems für einen Warenkorb möchte man nicht jedes Mal überprüfen ob der Warenkorb korrekt aus der Datenbank geladen werden kann.

Es kann auch sein das die Abhängigkeiten für die zu testende Einheit noch gar nicht existieren. Soll ein Client und ein Service parallel entwickelt werden einigt man sich zuerst auf eine Schnittstelle. Bis der Service läuft muss man mit dem Client gegen etwas testen können. Mocks können dieses „etwas“ sein und den Service simulieren.

Was zwischen 2 Systemen geht funktioniert genauso auch mit gewöhnlichen Klassen. Hier kann man ein Interface definieren und erzeugt daraus einen Mock bis man eine richtige Implementierung hat.

Moq verwenden

Moq installiert man am besten über NuGet. Man bekommt so die aktuelle Version und kann später sehr einfach das installierte Paket aktualisieren. Wer dies nicht will findet den Code auf der Projektseite. Die Referenz auf Moq braucht es nur im Testprojekt, das Projekt mit dem produktiven Code muss von Moq nichts wissen.

Bevor man mit Moq loslegt sollte man einen Blick auf den QuickStart Artikel im Wiki werfen. Dort werden die häufigsten Anwendungsfälle mit Codebeispielen gezeigt.

Für mein Beispiel nutze ich das Interface IPersonService und die Klasse Person:

public interface IPersonService
{
    Person GetPersonById(int id);
}

public class Person
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Ich möchte erreichen dass immer wenn ich die Methode GetPersonById(1) aufrufe eine bestimmte Instanz von Person zurückgegeben wird. Dazu genügt es die Person zu instantzieren und 2 Zeilen Code um Moq aufzusetzen:

[TestMethod]
public void ShowHowToMockAnInterface()
{
    Person person = new Person(){LastName = "Graber", FirstName = "Johnny"};

    var mock = new Mock<IPersonService>();
    mock.Setup(service => service.GetPersonById(1)).Returns(person);

    Assert.AreEqual(person, mock.Object.GetPersonById(1));
}

Wer sich bisher nicht mit Lambda-Ausdrücken beschäftigt hat wird vielleicht ein wenig verwirrt sein. Ich finde den Code so aber sehr leserlich und verständlich. Will man später seine Methode umbenennen wird mit den Tools von Visual Studio dieser Aufruf gefunden. Würde man statt dem Lambda einen String benutzen ist dies nicht immer der Fall.

Um eine realistischere Verwendung von Moq zu zeigen soll nun die Klasse Worker die Person holen. Diese kennt nur das Interface und weiss nichts von Moq:

public class Worker
{
    private readonly IPersonService _personService;

    public Worker(IPersonService personService)
    {
        _personService = personService;
    }

    public Person GetPerson()
    {
        return _personService.GetPersonById(1);
    }
}

Der dazugehörige Test sieht fast gleich aus:

[TestMethod]
public void ShowUsageOfMock()
{
    Person person = new Person() { LastName = "Graber", FirstName = "Johnny" };

    var mock = new Mock<IPersonService>();
    mock.Setup(service => service.GetPersonById(1)).Returns(person);   

    Worker worker = new Worker(mock.Object);

    Assert.AreEqual(person, worker.GetPerson());
}

Stabile Tests

Was mir an Moq besonders gefällt ist das Standardverhalten. Wird eine Methode des Interfaces aufgerufen die man nicht gemockt hat liefert Moq den Standardwert zurück. Wenn ich eine neue Methode im Interface hinzufüge und diese im Worker aufrufe funktionieren meine bisherigen Tests auch weiterhin.

public interface IPersonService
{
    Person GetPersonById(int id);

    bool IsActive(Person person); // Neue Methode
}

public class Worker
{
    ...
    public Person GetPerson()
    {
        Person p = _personService.GetPersonById(1);
        _personService.IsActive(p); // neuer Aufruf
        return p;
    }
}

Durch dieses Verhalten sind die Tests weniger Fehleranfällig. Wenn ich aber einen Fehler will sobald andere Methoden von meinem Mock aufgerufen werden kann ich dies im Konstruktor aktivieren:

var mock = new Mock<IPersonService>(MockBehavior.Strict);

Wo Moq an seine Grenzen stösst

Wie viele Mock-Frameworks kann auch Moq keine statischen Klassen und Methoden mocken. Arbeitet man an einem neuen Projekt kann man diese Einschränkung umgehen in dem man möglichst auf statischen Methoden verzichtet und wo nicht anders möglich diese in einen Wrapper packt.

Will man aber in einem grossen bestehenden Projekt mit vielen statischen Methoden mit Unit Tests beginnen ist die Einschränkung von Moq ein Problem. In dem Fall sollte man sich TypeMock und JustMock anschauen. Diese Frameworks umgehen die Einschränkungen mit statischen Klassen und Methoden indem sie sich sehr tief im .Net Framework einklinken und Methodenaufrufe umleiten.

Fazit

Moq ist ein sehr hilfreiches Werkzeug für Unit Tests. Es macht genau was man von ihm erwartet und braucht dazu nur eine minimale Konfiguration. Dadurch kann man sich auf das konzentrieren was man wirklich machen will: Mit Unit-Tests neue Funktionalität einbauen.