xUnit异步测试必须返回Task且用async修饰,禁用void;断言需await而非Wait/Result;参数化测试用Theory+InlineData;应统一使用ConfigureAwait(false)避免死锁。
xUnit 要求异步测试方法必须返回 Task(不能是 void),且需用 async 修饰。若写成 void,测试会“假成功”——即框架不等待异步操作完成就标记为通过。
public async Task MyTest()
public async void MyTest()(xUnit 不识别,测试立即结束)await 调用,再用 Assert;不要用 .Wait() 或 .Result,否则可能死锁(尤其在 UI 或 ASP.NET 同步上下文环境中)[TestCase] 的异步变体,参数化异步测试需用 [Theory] + [InlineData],每个用例仍需返回 Task
public class CalculatorTests
{
[Fact]
public async Task AddAsync_ReturnsSum()
{
var result = await new Calculator().AddAsync(2, 3);
Assert.Equal(5, result);
}
}

NUnit 3.0+ 支持 async Task 方法,但对 async void 也有一定容忍(不推荐)。关键差异在于:NUnit 允许测试方法返回 Task 或 void,但仅当返回 Task 时才真正等待异步完成。
async void,NUnit 可能不报错,但实际未等待异步逻辑,导致间歇性失败[TestCase] 和 [Theory] 均支持异步方法,只要签名是 async Task
await Dispatcher.InvokeAsync)在 NUnit 中比 xUnit 更容易“看似正常”,但这掩盖了线程模型问题[RequiresThread] 或用 ConfigureAwait(false) 避免隐式依赖两类框架下最典型的失败表现不是抛异常,而是“测试绿了但逻辑没执行完”。比如 HTTP 调用未发出去、数据库写入丢失、计时器未触发。
Assert 没被调用,或断言值始终是默认值(如 0、null)void 或未 await 关键调用,框架提前退出Console.WriteLine("done"),观察是否打印;或在被测异步方法内首行打日志,确认是否进入The test method returned void, but should return Task;NUnit 则更沉默,需靠行为观察应该,尤其是在共享测试基类或封装通用异步断言逻辑时。测试运行器(如 Visual Studio Test Explorer、dotnet test)通常不提供有意义的同步上下文,保留默认 ConfigureAwait(true) 只会增加调度开销,还可能引发意外死锁。
.ConfigureAwait(false)
SingleThreadSynchronizationContext)时才保留上下文Task + 正确 await + 避免上下文依赖”这三条线。漏掉任意一条,都可能让 bug 在 CI 上潜伏数周。