MockSequence is the little niche feature that led me to working on Moq in the first place. I was playing around with the idea of a push- based JSON parser that would invoke a callback for each token read from the input. In my unit tests, I needed to ensure that the right sequence of callbacks got invoked for a given JSON input string.

What is it?

MockSequence can be used to check whether several calls in a single mock are made in a specific order:

public interface IJsonTokenSink
{
    void OnBoolean(bool value);
    void OnColon();
    void OnComma();
    void OnLeftAngleBracket();
    void OnLeftCurlyBrace();
    void OnNull();
    void OnNumber(string value);
    void OnRightAngleBracket();
    void OnRightCurlyBrace();
    void OnString(string value);
    void OnCompleted();
}

const string input = "[ 42, true ]";

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

var seq = new MockSequence();
mock.InSequence(seq).Setup(m => m.OnLeftAngleBracket());
mock.InSequence(seq).Setup(m => m.OnNumber("42"));
mock.InSequence(seq).Setup(m => m.OnComma());
mock.InSequence(seq).Setup(m => m.OnBoolean(true));
mock.InSequence(seq).Setup(m => m.OnRightAngleBracket());
mock.InSequence(seq).Setup(m => m.OnCompleted());

var tokenizer = new JsonTokenizer(tokenSink: mock.Object);
tokenizer.Tokenize(input);

Why is it problematic?

Unfortunately, MockSequence as it is today is only of limited use, and extending it to something more powerful may turn out to be a bigger effort than restarting from scratch.

For one thing, there’s no way to ensure that all configured calls in the sequence have been made. You can guard against superfluous calls using MockBehavior.Strict; but you cannot easily detect missing ones because mock.Verify[All] won’t work here, since mock sequences are using conditional setups under the hood (and those get excluded from verification).

Another problem is that sequences cannot easily span across several mocks. It’s possible to call .InSequence(seq) on different mocks, but the problem is that noone will see the complete invocation sequence; each mock will only see its own invocations in the sequence.

Like I mentioned in moq/moq4#75, those issues with MockSequence could perhaps be solved by recording invocations on the sequence object itself, and by adding .Verify[All] methods to it, as well.

Because of the way how MockSequence uses conditional setups in its implementation, it isn’t thread-safe. Sequence setups were once based on conditional setups, too, and they also weren’t thread-safe. This was changed in moq/moq4#6281f4e. Unfortunately, the same kind of change is not possible for MockSequence. I’ll hint at the reason for this towards the end of this post.

Finally (but that is a very minor nit), it can be potentially confusing to Moq newcomers that there are two different kinds of “sequences”:

  • “setup sequences” aka mock.InSequence(...)
  • “sequence setups” aka mock.SetupSequence(...)

Replacement

The example given above could be rewritten without MockSequence as follows:

var seq = 0;
mock.When(() => seq == 0).Setup(m => m.OnLeftAngleBracket()) .Callback(() => seq++);
mock.When(() => seq == 1).Setup(m => m.OnNumber("42"))       .Callback(() => seq++);
mock.When(() => seq == 2).Setup(m => m.OnColon())            .Callback(() => seq++);
mock.When(() => seq == 3).Setup(m => m.OnBoolean(true))      .Callback(() => seq++);
mock.When(() => seq == 4).Setup(m => m.OnRightAngleBracket()).Callback(() => seq++);
mock.When(() => seq == 5).Setup(m => m.OnCompleted())        .Callback(() => seq++);

Quite a bit of repetition—MockSequence did help there!— but perfectly feasible, so I’ll proceed with its removal.

(Incidentally, this rewrite uncovers the reason why sequences aren’t thread-safe: the comparison test on seq in .When and the update of seq in .Callback do not happen as an atomic operation; so it’s possible that one of these setups will trigger twice and the succeeding one might get skipped.)

Removal

Removing MockSequence is straightforward (see commit 958a982) because it is really just a slim layer on top of conditional setups.