alamoot
alamoot

Reputation: 2144

How to use Callback on Moq's SetUpSequence?

I'm using Moq version 4.8 and have a method to mock and assert its parameter. I started with this mocked method:

mock.Setup(m => m.Update(It.IsAny<MyClass>))
    .Callback((MyClass c) =>
    {
        // some assertions
    })
    .Returns(Task.FromResult(updatedClass));

where I update an object of type MyClass and do a number of assertions on that object. This works just fine.

I've just added logic to the method calling Update to retry calling it if exceptions are thrown. So I want to implement a new unit test that throws exceptions a few times and then returns and be able to do the assertions like before. So I tried SetupSequence as follows:

mock.SetupSequence(m => m.Update(It.IsAny<MyClass>))
    .Throws(new Exception("test exception 1"))
    .Throws(new Exception("test exception 2"))
    .Callback((MyClass c) =>
    {
        // some assertions
    })
    .Returns(Task.FromResult(updatedClass));

But ISetupSequence doesn't support Callback. Is there a way to mock Throws and Returns calls in order while keeping a pre call Callback to Returns?

Upvotes: 12

Views: 11903

Answers (3)

Ben Wesson
Ben Wesson

Reputation: 639

I have used this approach to capture each instance of a request to a method and also return a sequence of values.

var requests = new List<SomeRequest>();
var sequence = new MockSequence();
foreach (var response in responses)
{
    someMock.InSequence(sequence)
        .Setup(x => x.SomeMethod(It.IsAny<SomeRequest>()))
        .Callback<SomeRequest>(r => requests.Add(r))
        .ReturnsAsync(response);
}

Given some list of pre-defined responses, the above will capture all the requests made to a mocked method and return the sequence of responses.

Upvotes: 6

michael yin
michael yin

Reputation: 249

You can use MockSequence, so that you can add Callback after .Setup().

var mock = new Mock<IFoo>(MockBehavior.Strict);
var sequence = new MockSequence();

_fooService.InSequence(sequence).Setup(x => x.FooMethod(a)).ReturnsAsync(b);
_barService.InSequence(sequence).Setup(x => x.BarMethod(c)).ReturnsAsync(d);
_bazService.InSequence(sequence).Setup(x => x.BazMethod(e)).ReturnsAsync(f);

Upvotes: 9

alamoot
alamoot

Reputation: 2144

For the time being I've been doing the this as a work around:

int callCount = 0;
mock.Setup(m => m.Update(It.IsAny<MyClass>))
    .Callback((MyClass c) =>
    {
        callCount++;
        if (callCount <=2)
        {
            throw new Exception($"Test Exception #{callCount}");
        }
        else
        {
            callCount = 0; // this is needed if you're gonna use callCount for multiple setups
            // some assertions
        }
    })
    .Returns(Task.FromResult(updatedClass));

Feels like a hack, but does what I'm looking for.

Upvotes: 5

Related Questions