Moq 模拟 async 方法必须返回 Task 或 Task
Moq 无法直接模拟
async void方法(也不该这么做),所有被模拟的异步方法签名必须是
Task或
Task<t></t>。如果你看到
NotSupportedException: Cannot setup method with return type Void,大概率是接口或虚方法声明成了
async void DoSomething()—— 这属于设计错误,需先改为
Task DoSomething()。
常见错误场景:在接口中定义了
void SaveAsync(...)却期望 Moq 返回可 await 的结果;或者误把同步方法标记为
async但没改返回类型。 接口方法必须声明为
Task/
Task<t></t>,不能是
void被 mock 的类中对应方法需是
virtual或实现接口,否则 Moq 无法重写 不要在
Setup中直接
await,Moq 的
ReturnsAsync和
Returns是同步配置行为
用 ReturnsAsync 正确模拟 Task 返回值
ReturnsAsync是 Moq 提供的语法糖,等价于
Returns(Task.FromResult(value)),专用于简化
Task<t></t>的模拟。它内部自动包装成已完成的
Task,不会真正启动异步流程,适合单元测试中快速构造确定性响应。
注意:如果返回的是
null且泛型参数为引用类型,需显式写
ReturnsAsync((string)null),否则 C# 类型推导可能失败。
var mockService = new Mock<IDataService>();
mockService.Setup(x => x.FetchUserAsync(123))
.ReturnsAsync(new User { Id = 123, Name = "Alice" });
<p>// 测试代码中可正常 await
var user = await mockService.Object.FetchUserAsync(123); // 返回预设对象模拟 Task(无返回值)用 Returns + Task.CompletedTask
对于声明为
Task DoWorkAsync()的方法,不能用
ReturnsAsync(它只接受
T参数),而应使用
Returns(Task.CompletedTask)。这是最轻量、最推荐的方式 —— 它返回一个已成功完成的静态
Task实例,零分配、无调度开销。
别用
Task.Run(() => {}) 或 Task.Delay(0)替代,它们会触发线程池调度,增加不确定性,还可能干扰测试时序判断。
mockService.Setup(x => x.LogAsync("event"))
.Returns(Task.CompletedTask);
<p>// 调用后立即完成,不阻塞
await mockService.Object.LogAsync("event"); // 成功返回需要验证异步执行顺序?小心 SetupSequence 和 await 时机
SetupSequence可用于模拟多次调用返回不同结果,但它本身不感知 await。如果你在测试中连续 await 同一 mock 方法,要确保每次 await 都拿到预期值 —— 这依赖于调用次数,而非“异步完成时间”。Moq 不模拟真实异步延迟,所以不要指望靠它测“并发竞争”或“超时逻辑”。
真正需要控制异步行为(如延迟、取消、异常)时,应改用
TaskCompletionSource<t></t>手动构造可控制的
Task,再传给
Returns:
var tcs = new TaskCompletionSource<string>();
mockService.Setup(x => x.LoadConfigAsync()).Returns(tcs.Task);
<p>// 后续在测试中可手动完成:tcs.SetResult("config.json");
// 或取消:tcs.SetException(new OperationCanceledException());这种写法灵活但复杂,多数场景用
ReturnsAsync和
Task.CompletedTask就够了;一旦开始手动管理
TaskCompletionSource,就得自己处理线程安全和状态一致性 —— 这往往是被忽略的复杂点。
