Nico
Nico

Reputation: 574

How can I mock an async protected method that has a parameter?

Here's my class to test:

namespace ClassLibrary1
{
    public class MyBase
    {
        public async Task DoSomething (MyContext context) => await DoSomethingInternal (context);
        public async Task DoSomething() => await DoSomethingInternal();

        protected virtual async Task DoSomethingInternal(MyContext context) { }
        protected virtual async Task DoSomethingInternal() { }
    }

    public class MyContext { }

    public class MyClass : MyBase
    {
        protected override Task DoSomethingInternal(MyContext context) => Task.CompletedTask;
        protected override Task DoSomethingInternal() => Task.CompletedTask;
    }
}

And here's the test code:

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        [ExpectedException(typeof(TaskCanceledException))]
        public async Task TestMethod1()
        {
            var mock = new Mock<MyClass>
            {
                CallBase = true
            };
            mock.Protected().Setup<Task>("DoSomethingInternal", new MyContext()).ThrowsAsync(new TaskCanceledException());
            var obj = mock.Object;

            await obj.DoSomething(null);
        }

        [TestMethod]
        [ExpectedException(typeof(TaskCanceledException))]
        public async Task TestMethod2()
        {
            var mock = new Mock<MyClass>
            {
                CallBase = true
            };
            mock.Protected().Setup<Task>("DoSomethingInternal").ThrowsAsync(new TaskCanceledException());
            var obj = mock.Object;

            await obj.DoSomething();
        }
    }
}

The result shows that the first test fails and the second succeeds. Debugging shows that the first test's call to DoSomethingInternal(context) returns Task.CompletedTask, instead of throws the exception.

So how can I make it throw?

Upvotes: 4

Views: 3866

Answers (1)

Nkosi
Nkosi

Reputation: 247068

The argument passed in the setup does not match the instance passed when the test is exercised. You would need to use an argument matcher in this scenario to allow the code to flow as expected.

Setting expectations for protected members, if you need argument matching, you MUST use ItExpr rather than It

var mock = new Mock<MyClass>() {
    CallBase = true
};

mock.Protected()
    .Setup<Task>("DoSomethingInternal", ItExpr.IsAny<MyContext>())
    .ThrowsAsync(new TaskCanceledException());

var obj = mock.Object;

await obj.DoSomething(null);

Reference Moq Quickstart: Miscellaneous

Upvotes: 5

Related Questions