异步测试必须用async task方法声明,避免void或同步task;禁用.result/wait()防死锁;mock需返回task.fromresult或returnsasync;断言须用await assert.throwsasync。

async方法测试必须用async Task方法声明
同步的void或Task测试方法无法正确等待异步操作完成,会导致测试提前结束、断言失效或偶发失败。xUnit/NUnit/MSTest都要求测试方法本身是async Task,否则await会被当作同步调用处理。
- ✅ 正确写法:
public async Task MyMethod_ShouldReturnExpectedResult() - ❌ 错误写法:
public void MyMethod_ShouldReturnExpectedResult()或public Task MyMethod_ShouldReturnExpectedResult() - 如果用 MSTest,还需在方法上加
[TestMethod];xUnit 则用[Fact]或[Theory]
避免直接调用Result或Wait()引发死锁
在测试中对Task对象调用.Result或.Wait(),尤其在有同步上下文(如默认的 UI 线程或 ASP.NET 同步上下文)的测试运行器里,极易触发死锁——因为 await 在捕获上下文后尝试回调回原上下文,而该上下文正被Wait()阻塞。
- 永远用
await myAsyncMethod(),而不是myAsyncMethod().Result - 若需兼容旧框架(如 .NET Framework 4.6.1 以下),可临时用
ConfigureAwait(false)降低风险,但治标不治本 - Mock 异步依赖时,确保返回的是真正可 await 的
Task,比如 Moq 中用Task.FromResult(…)而非直接 return 值
Mock 异步依赖要用 Task.FromResult 或 ReturnsAsync
测试 async 方法时,常需隔离外部 I/O(如 HTTP 调用、数据库访问)。若 mock 返回普通值而非 Task,运行时会报错“无法将 T 隐式转换为 Task
- Moq 用户:用
mock.Setup(x => x.GetAsync()).ReturnsAsync(result)(推荐)或Task.FromResult(result) - NSubstitute 用户:用
substitute.GetAsync().Returns(Task.FromResult(result)) - 注意:不要写
Returns(result),这会让方法签名不匹配,编译可能过,但运行时抛InvalidCastException
异常断言要用 await + Assert.ThrowsAsync
异步方法抛出的异常被包装在 Task 内部,不能用 Assert.ThrowsException<exceptiontype>(() => …)</exceptiontype> 这种同步断言方式——它只会捕获同步阶段抛出的异常,而 await 后的异常根本不会触发。
- ✅ 正确:
await Assert.ThrowsAsync<invalidoperationexception>(async () => await target.DoWorkAsync())</invalidoperationexception> - ❌ 错误:
Assert.ThrowsException<invalidoperationexception>(() => target.DoWorkAsync().Wait())</invalidoperationexception>(绕过 await,且易死锁) - 部分测试框架(如 xUnit)的
ThrowsAsync是泛型方法,类型参数必须精确匹配,比如ArgumentException和ArgumentNullException不等价
async Task 测试方法 + await + ThrowsAsync 这套组合,别为了“看着快”去碰 Result 或 GetAwaiter().GetResult()。










