对等待的模拟对象的异步回调
我试图模拟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