模拟异步方法

本文关键字:异步方法 模拟 | 更新日期: 2023-09-27 18:30:10

我们正在使用MSTest和Moq为异步代码编写单元测试。

所以我们有一些代码看起来像:

var moq = new Mock<Foo>();
moq.Setup(m => m.GetAsync())
   .Returns(Task.FromResult(10));

或者像这样在有Moq 最新版本的项目上

var moq = new Mock<Foo>();
moq.Setup(m => m.GetAsync())
   .ReturnsAsync(10);

查看ReturnsAsync:的Moq实现

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class
{
  TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>();
  completionSource.SetResult(value);
  return mock.Returns(completionSource.Task);
}

这两种方法在本质上似乎是一样的。两者都创建一个TaskCompletionSource,调用SetResult并返回Task

到目前为止还不错。

但短期运行的async方法被优化为同步运行。这似乎意味着TaskCompletionSource总是同步的,这似乎也表明上下文处理和任何可能发生的相关问题永远不会发生。

因此,如果我们有一些代码正在执行一些async no no,比如混合awaitsWait()Result,那么这些问题将不会在单元测试中检测到。

创建一个总是产生控制权的扩展方法有什么好处吗?类似于:

public async Task<T> ReturnsYieldingAsync<T>(T result)
{
    await Task.Yield();
    return result;
}

在这种情况下,我们将有一个保证异步执行的方法。

感知到的优势是检测到错误的异步代码。例如,它可以在单元测试期间捕获任何死锁或异常吞咽。

我不能百分之百肯定是这样,所以我真的很想听听社会人士的意见。

模拟异步方法

当我四年前第一次开始谈论测试异步代码时(!),我鼓励开发人员沿着异步轴测试他们的mock。也就是说,沿着结果轴(成功、失败)和异步轴(同步、异步)进行测试。

不过,随着时间的推移,我在这方面放松了。当涉及到测试我自己的代码时,我实际上只测试同步成功/失败路径,除非有明确的理由也测试该代码的异步成功路径。我再也不关心异步故障测试了。