如何Mock依赖Cookie与Session的控制器Action并解决空引用异常?
问题分析与解决方案
你的测试代码触发空引用异常的核心原因是:先给控制器配置了带有效HttpContext的ControllerContext,紧接着又用空的Mock<ControllerContext>.Object覆盖了它,导致控制器的Request属性为null。此外,你还需要正确配置Cookie和Session才能让测试完整运行。
1. 修正ControllerContext赋值错误
删掉覆盖ControllerContext的无效代码,保留带DefaultHttpContext的正确配置:
// 移除这行错误代码: // azureReportControllerUnderTest.ControllerContext = context.Object; // 保留正确的上下文配置 azureReportControllerUnderTest.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext(), };
2. 配置Request的Cookie值
GetCurrentTenant会读取Request.Cookies["selectedtenant"],需要在DefaultHttpContext中添加对应Cookie:
var httpContext = new DefaultHttpContext(); // 给Request添加目标Cookie httpContext.Request.Cookies["selectedtenant"] = "3c6b60a7-8d30-4da3-bfdc-00691e899b5f"; azureReportControllerUnderTest.ControllerContext = new ControllerContext { HttpContext = httpContext, };
如果要测试无Cookie场景(即触发GetUserDefaultTenant分支),可以创建控制器的测试子类重写该protected方法:
// 测试用子类,重写GetUserDefaultTenant返回固定Guid public class TestableAzureReportsController : AzureReportsController { public TestableAzureReportsController(ILogger<AzureReportsController> logger, IPrismRepository prismRepo, IReportsRepository reportsRepo) : base(logger, prismRepo, reportsRepo) { } protected override Guid GetUserDefaultTenant() { return Guid.Parse("3c6b60a7-8d30-4da3-bfdc-00691e899b5f"); } } // 测试时使用这个子类替代原控制器 var azureReportControllerUnderTest = new TestableAzureReportsController(mockIlogger.Object, mockPrismRepository.Object, mockReportRepository.Object);
3. 配置Session支持
DefaultHttpContext默认未启用Session,需要手动配置Session服务及上下文:
// 注册Session所需服务 var services = new ServiceCollection(); services.AddSession(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new DefaultHttpContext(); httpContext.RequestServices = serviceProvider; // 初始化Session await httpContext.Session.LoadAsync();
修改后的完整测试代码
[TestMethod] public async Task GetCustomDateResourcesReturnsExcelWorkBook() { var mockPrismRepository = new Mock<IPrismRepository>(); var mockIlogger = new Mock<ILogger<AzureReportsController>>(); var mockReportRepository = new Mock<IReportsRepository>(); DateTime from = DateTime.Today; DateTime to = from.AddDays(30); var tenantId = Guid.Parse("3c6b60a7-8d30-4da3-bfdc-00691e899b5f"); var excelWorkBook = new XLWorkbook(); // 注意参数顺序:与Action中的调用保持一致(selectedTenant, startingDate, endingDate) mockReportRepository.Setup(r => r.GetCustomDateResources(tenantId, from, to)) .Returns(excelWorkBook); // 配置Session服务 var services = new ServiceCollection(); services.AddSession(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new DefaultHttpContext(); httpContext.RequestServices = serviceProvider; await httpContext.Session.LoadAsync(); // 添加目标Cookie httpContext.Request.Cookies["selectedtenant"] = tenantId.ToString(); var azureReportControllerUnderTest = new AzureReportsController(mockIlogger.Object, mockPrismRepository.Object, mockReportRepository.Object); azureReportControllerUnderTest.ControllerContext = new ControllerContext { HttpContext = httpContext, }; var result = azureReportControllerUnderTest.ResourcesCustomReports(from, to); Assert.IsNotNull(result); excelWorkBook.Dispose(); azureReportControllerUnderTest.Dispose(); }
额外注意事项
- 检查
GetCustomDateResources的参数顺序:你的原测试代码中Setup的参数顺序与Action调用不匹配,已在修正代码中调整。 - 若
GetUserDefaultTenant依赖其他服务,需同步Mock或配置这些服务的测试实例。
内容的提问来源于stack exchange,提问作者Tim Whatley
相关产品推荐
相关产品推荐

