使用NUnit和NSubstitute对ASP.NET Core中间件进行单元测试
解决ASP.NET Core中间件单元测试中HttpContext与异常捕获的问题
首先,咱们先理清你当前测试的核心问题:你的测试代码里,传入的next委托并没有抛出异常,所以中间件的try块执行完_next(context)后就直接结束了,根本不会进入catch分支,自然也就不会调用LogException方法。这才是测试不生效的主要原因,而不是HttpContext的模拟问题。
不过先回答你关于HttpContext的疑问:NUnit本身没有专门提供创建真实HttpContext的工具,但ASP.NET Core自带的DefaultHttpContext就是一个真实的、轻量级的HttpContext实现,你在测试里已经用到它了——它不是模拟对象,完全可以满足单元测试的需求。如果后续需要更复杂的上下文(比如配置请求路径、响应状态码等),直接给DefaultHttpContext的属性赋值就行,不需要额外的基础设施。
接下来咱们修正你的测试代码,让它能正确触发中间件的异常处理逻辑:
修正后的测试代码
[TestFixture] public class ExceptionHelperTests { private IErrorRepository errorRepository; private ExceptionHandler handler; [SetUp] public void Setup() { errorRepository = Substitute.For<IErrorRepository>(); } // 注意:把async void改成async Task,NUnit对async void的支持很差,会导致测试无法正确等待异步操作完成 [Test] public async Task Given_AnExceptionHappens_Then_ItShouldBeLogged() { // Arrange const string username = "aUser"; // 创建真实的ClaimsIdentity,而不是完全模拟ClaimsPrincipal,避免Identity.Name返回null var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }, "TestAuth"); var user = new ClaimsPrincipal(identity); handler = new ExceptionHandler( next: async (innerHttpContext) => { innerHttpContext.User = user; // 关键:让next委托抛出异常,触发中间件的catch分支 throw new InvalidOperationException("Test exception"); }, repository: errorRepository); // Act await handler.Invoke(new DefaultHttpContext()); // Assert errorRepository.Received().LogException( "An unhandled exception has occurred.", Arg.Any<InvalidOperationException>(), username); } }
关键修改点说明
- 将
async void改为async Task:NUnit无法正确跟踪async void方法的异步完成状态,会导致测试在异步操作结束前就完成,进而断言失败。 - 让
next委托抛出异常:这是触发中间件catch分支的核心,没有异常的话,中间件只会执行try块里的逻辑。 - 使用真实的
ClaimsIdentity:完全模拟ClaimsPrincipal可能会导致Identity.Name返回null(因为模拟对象的默认行为可能不符合预期),用真实的ClaimsIdentity可以更可靠地设置用户名。
关于HttpContext的补充说明
如果你的中间件需要操作响应(比如写入响应内容、设置状态码),DefaultHttpContext的Response属性是真实的HttpResponse实现,完全可以满足需求。你可以直接对它进行配置,比如:
var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); // 配置响应流,避免写入时抛出异常
这样修改后,你的测试应该就能正确验证异常是否被记录了。
内容的提问来源于stack exchange,提问作者Red
相关产品推荐
相关产品推荐

