Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 589
Nico Avatar asked Oct 16 '25 22:10

Nico


1 Answers

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

like image 67
Nkosi Avatar answered Oct 19 '25 13:10

Nkosi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!