Moq'ing Successive Calls to a Method - void method exception handling

My last post expanded on a piece of code I found on Phil Haack's blog that extends Moq to allow you to return different values on each successive call to a method - I also improved it to allow you mock up any of the iterations to throw an exception in place of the return value.

This is great for methods that actually return values, but what happens when you want to throw optional exceptions when you invoke void methods in Moq? Well, void methods on Moq mocks return ISetup<TMock> instead of ISetup<TMock, TResult>, which means that you can't use the ReturnsInOrder method to handle exceptions. Normally, you would just append a .Throws() to the respective Setup method, but Throws() has no params[] parameter, and no Action based override (as Returns() does), so the only way to apply this kind of rule is with recursive callbacks, which are both cumbersome and hard to read. This is demonstrated below; I'm mocking up a call to an FTP class. The first and third calls to this method will return void (success), the second call will throw an exception:

mockFTP
    .Setup(x => x.Put(It.IsAny<string>(), It.IsAny<string>()))
    .Callback(() =>
        mockFTP
            .Setup(x1 => x1.Put(It.IsAny<string>(), It.IsAny<string>()))
            .Callback(() =>
                mockFTP
                    .Setup(x2 => x2.Put(It.IsAny<string>(), It.IsAny<string>())))
            .Throws(expectedException));

Wouldn't it be much nicer if you could do something like this?

mockFTP
    .Setup(x => x.Put(It.IsAny<string>(), It.IsAny<string>()))
    .ThrowsInOrder(null, expectedException, null);

The answer is "Yes", in case you've forgotten what you're agreeing to. Well... now you can! The answer is to create another extension method specifically for ISetup<TMock> that uses the same kind of queuing mechanism in Phil's original post, but wraps a load of recursive callbacks:

        public static IThrowsResult ThrowsInOrder<TMock>(this ISetup<TMock> setup,
            params Exception[] exceptions) where TMock: class {
            var queue = new Queue(exceptions);

            return setup.Callback(() => {
                var result = queue.Dequeue() as Exception;

                if (result == null) {
                    setup.Throws(null);
                    return;
                }

                setup.Throws(result);
            });
        }

Enjoy!

Popular posts from this blog

How I Learned to Lose Weight and Love Exercise (again)

GDPR: Application Password Security in 2018

AutoMapper: UseValue vs ResolveUsing vs MapFrom