Reihenfolge von Methodenaufrufen mit Moq testen -> Bug

Ich nutze hauptsächlich das Isolationframework Moq. Mit dem man ohne großen Aufwand Fake-Instanzen von Interfaces erstellen und somit seine Unit Tests übersichtlicher gestallten kann. Eine Sache hat mich bisher aber immer genervt und obwohl oder gerade weil dieser Umstand schon länger besteht, will ich hier genauer darauf eingehen.

Es geht mir dabei um das Überprüfen der Reihenfolge von Methodenaufrufen. Dies ist zwar theoretisch möglich, praktisch mit Hilfe der Bordmittel von Moq aber nicht machbar. Zugegeben, dies ist auch kein all zu häufiger Anwendungsfall, kann aber schon mal vorkommen und dann sitzt man eben auf dem Trockenen.

Um zu verstehen was ich meine, sei folgendes Interface und folgende zu testende Klasse gegeben.

public interface IFoo
{
     void First();
     void Second();
}

public class Bar
{
     public static void DoSomething(IFoo fooOne, IFoo fooTwo)
     {
         fooOne.First();
         fooTwo.Second();
     }

     public static void DoSomething(IFoo foo)
     {
         foo.First();
         foo.Second();
     }
}

Im ersten Fall wollen wir also prüfen ob die Methode First vom ersten Fake aufgerufen wird und danach die Second-Methode vom zweiten Fake. Der Test dazu sieht wie folgt aus und funktioniert auch einwandfrei.

[TestMethod]
public void DoesWork()
{
      var mockOne = new Mock<IFoo>(MockBehavior.Strict);
      var mockTwo = new Mock<IFoo>(MockBehavior.Strict);
      var sequence = new MockSequence();

      mockOne.InSequence(sequence).Setup(x => x.First());
      mockTwo.InSequence(sequence).Setup(x => x.Second());

      Bar.DoSomething(mockOne.Object, mockTwo.Object);
}

Wollen wir nun aber testen ob die Reihenfolge auch bei ein und dem selben Fake geprüft werden kann, erleben wir eine böse Überraschung. Es geht nämlich nicht! Sobald Second aufgerufen wird, gibt es eine Exception die sagt, dass keine geeignete Konfiguration vorliegt.

[TestMethod]
public void DoesNotWork()
{
     var mock = new Mock<IFoo>(MockBehavior.Strict);
     var sequence = new MockSequence();

     mock.InSequence(sequence).Setup(x => x.First());
     mock.InSequence(sequence).Setup(x => x.Second());

     Bar.DoSomething(mock.Object);
}

Gesagt sei dazu, dass dieser Bug nicht neu ist. Es gibt sogar schon seit einiger Zeit einen Fix dazu, leider hat dieser den Weg aber nicht in Version 4.0 geschafft. Wann es dazu Abhilfe gibt ist auch noch nicht klar. Solange dem so ist wird man um einen gewissen Workarround leider nicht drum herum kommen. Diesen findet man zum Beispiel hier bzw. hier.

Das obige Beispiel sieht dann mit der Erweiterung wie folgt aus.

var mock = new Mock<IFoo>();
using (Sequence.Create())
{
     mock.Setup(x => x.First()).InSequence();
     mock.Setup(x => x.Second()).InSequence();

     Bar.DoSomething(mock.Object);
}

Das Vorgehen erinnert ein Wenig an Record & Replay von Rhino und ist meiner Meinung nach nicht sonderlich schön aber es funktioniert. Das Assert wird übrigens beim verlassen des Blocks ausgeführt.