模拟对象上的异步回调不等待

Async callback on mocked object not awaiting

我正在尝试为单元测试模拟一个复杂的情况:

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

其中 IController 有一个 void 方法 Interrupt(Func<Task>> f),它将一些待完成的工作排队。

我的测试对象确实调用了 Interrupt(),我可以像这样验证调用:

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

...但是当在回调中调用参数 Func<Task> 时,await 关键字在 Task 中不受尊重:测试的执行在 Task 完成(尽管回调中有 await)。一个症状是在 Interrupt() Task 参数中添加 await Task.Delay(1000) 会将通过的测试变成失败的测试。

此行为是由于测试期间处理线程或 Tasks 的细微差别造成的吗?还是最小起订量的限制?我的测试方法有这个签名:

[Test]
public async Task Test_Name()
{
}

Callback 不能 return 值,因此不应用于执行异步代码(或需要 return 值的同步代码)。 Callback 是一种 "injection point",您可以挂钩它以检查传递给 方法的参数,但不能修改它 return 的内容。

如果你想要一个 lambda 模拟,你可以使用 Returns:

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

(我假设 Interrupt returns Task)。

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

是的,因为 Callback 不能 return 一个值,它总是被输入为 Action/Action<...>,所以你的 async lambda 结束了作为 async void 方法,all the problems that brings(如我的 MSDN 文章中所述)。

更新:

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

你可以嘲笑那个行为,如果这行得通的话:

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