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

我试图模拟unit testing的复杂情况:

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

IController有一个void方法Interrupt(Func> f) ,它将一些工作排​​队。

我正在测试的对象会调用Interrupt() ,我可以像这样validation调用:

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

…但是当在回调中调用参数Func ,在Task不遵守await关键字:在Task完成之前继续执行测试(尽管在回调中await )。 其中一个症状是在Interrupt() Task参数中添加await Task.Delay(1000)会将通过的测试转换为失败的测试。

这种行为是由于测试期间如何处理线程或Task的细微差别? 或Moq的限制? 我的测试方法有这个签名:

 [Test] public async Task Test_Name() { } 

Callback不能返回值,因此不应该用于执行异步代码(或需要返回值的同步代码)。 Callback是一种“注入点”,您可以将其挂钩以检查传递方法的参数,但不能修改它返回的内容。

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

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

(我假设Interrupt返回Task )。

在任务完成之前继续执行测试(尽管在回调中等待)。

是的,因为Callback不能返回值,所以它总是输入为Action / Action<...> ,所以你的async lambda最终成为一个async void方法, 带来了所有问题 (如我的MSDN文章中所述) 。

更新:

Interrupt()实际上是一个void方法:它的作用是对函数(参数)进行排队,直到IController可能会被打扰停止它正在做的事情。 然后它调用函数 – 它返回一个任务 – 并等待该任务

你可以模仿那种行为,如果这样可行:

 List> queue = new List>(); _mockController.Setup(tc => tc.Interrupt(It.IsAny>())) .Callback>(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