不等待模拟对象的异步回调 [英] Async callback on mocked object not awaiting
问题描述
我正在尝试模拟一种复杂的情况以进行单元测试:
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());
IController
具有void方法Interrupt(Func<Task>> f)
的地方,该方法将要完成的工作排队.
Where IController
has a void method Interrupt(Func<Task>> f)
, which queues some work to be done.
我的被测试对象确实调用了Interrupt()
,我可以像这样验证该调用:
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);
...但是在回调中调用参数Func<Task>
时,在Task
中不使用await
关键字:测试的执行在Task
完成之前继续进行(尽管Interrupt()
Task
参数中添加await Task.Delay(1000)
会将通过测试变成失败测试.
...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.
这种行为是由于在测试过程中如何处理线程或Task
的细微差别引起的吗?还是起订量的限制?我的测试方法具有以下特征:
Is this behavior due to a nuance of how threads or Task
s are handled during test? Or a limitation of Moq? My test methods have this signature:
[Test]
public async Task Test_Name()
{
}
推荐答案
Callback
无法返回值,因此不应用于执行异步代码(或需要返回值的同步代码). Callback
是一种注入点",您可以将其插入以检查传递给该方法的参数,而不修改返回的结果.
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.
如果要使用lambda模拟,则可以使用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());
(我假设Interrupt
返回Task
).
在任务完成之前(尽管在回调中等待),测试的执行继续进行.
the execution of the test continues before the Task finishes (despite the await in the callback).
是的,由于Callback
无法返回值,因此始终将其键入为Action
/Action<...>
,因此您的async
lambda最终成为了async void
方法,并带有
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).
更新:
Interrupt()实际上是一个void方法:它所做的是将函数(参数)排队,直到可以烦扰IController停止其正在执行的操作为止.然后,它调用该函数-返回一个Task-并等待该任务
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
这篇关于不等待模拟对象的异步回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!