Async callback on mocked object not awaiting

六眼飞鱼酱① 提交于 2019-12-10 13:20:07

问题


I am attempting to mock a complicated situation for unit testing:

_mockController = new Mock<IController>();
_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>()))
    .Callback<Func<Task>>(async f => await f.Invoke());

Where IController has a void method Interrupt(Func<Task>> f), which queues some work to be done.

My objects under test do call Interrupt(), and I can verify the call like so:

_mockController.Verify(tc => tc.Interrupt(It.IsAny<Func<Task>>()), Times.Once);

...but when the argument Func<Task> is invoked in the callback, the await keyword is not respected in the Task: the execution of the test continues before the Task finishes (despite the await in the callback). One symptom of this is that adding an await Task.Delay(1000) in the Interrupt() Task argument turns a passing test into a failing test.

Is this behavior due to a nuance of how threads or Tasks are handled during test? Or a limitation of Moq? My test methods have this signature:

[Test]
public async Task Test_Name()
{
}

回答1:


Callback can't return a value, and thus shouldn't be used to execute asynchronous code (or synchronous code that needs to return a value). Callback is a kind of "injection point" that you can hook into to examine the parameters passed to the method, but not modify what it returns.

If you want a lambda mock, you can just use Returns:

_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>()))
    .Returns(async f => await f());

(I'm assuming Interrupt returns Task).

the execution of the test continues before the Task finishes (despite the await in the callback).

Yes, since Callback can't return a value, it's always typed as Action/Action<...>, so your async lambda ends up being an async void method, with all the problems that brings (as described in my MSDN article).

Update:

Interrupt() is actually a void method: what it does is queue the function (the argument) until the IController can be bothered to stop what it is doing. Then it invokes the function--which returns a Task--and awaits that Task

You can mock that behavior then, if this would work:

List<Func<Task>> queue = new List<Func<Task>>();
_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>()))
    .Callback<Func<Task>>(f => queue.Add(f));

... // Code that calls Interrupt

// Start all queued tasks and wait for them to complete.
await Task.WhenAll(queue.Select(f => f()));

... // Assert / Verify


来源:https://stackoverflow.com/questions/36583780/async-callback-on-mocked-object-not-awaiting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!