模拟对象上的异步回调不等待
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)
会将通过的测试变成失败的测试。
此行为是由于测试期间处理线程或 Task
s 的细微差别造成的吗?还是最小起订量的限制?我的测试方法有这个签名:
[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
我正在尝试为单元测试模拟一个复杂的情况:
_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)
会将通过的测试变成失败的测试。
此行为是由于测试期间处理线程或 Task
s 的细微差别造成的吗?还是最小起订量的限制?我的测试方法有这个签名:
[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