不等待模拟对象的异步回调 [英] Async callback on mocked object not awaiting

查看:66
本文介绍了不等待模拟对象的异步回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试模拟一种复杂的情况以进行单元测试:

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 Tasks 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆