You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6中用MOQ+Xunit模拟含RouteData.Values的Controller Action遇空引用

问题:测试Controller Action时RouteData.Values抛出空引用异常

我正在尝试模拟一个使用RouteData.Values的Controller Action方法,运行测试时,Action方法中var countryCode = (string)this.RouteData.Values["CountryCode"]行抛出"Object reference not set to an instance of an object."空引用异常。相关代码如下:

待测试的Action方法

public IActionResult Get(long siteId)
{
    //Get all the Reports
    var countryCode = (string)this.RouteData.Values["CountryCode"];
    var microMarketReport = _microMarketPriceOverviewBusiness.GetMicroMarketOverviewReport(siteId, countryCode).GetAwaiter().GetResult();
    return Ok(microMarketReport);
}

测试方法

[Fact]
public void Get_OK()
{
    // Arrange
    MockMicroMarketPriceOverviewBusiness.Setup(x => x.GetMicroMarketOverviewReport(It.IsAny<long>(),It.IsAny<string>()))
        .Returns(Task.Run(() => new MicroMarketOverviewModel()));
    var unitUnderTest = CreateMicroMarketPriceOverviewController();

    var routeData = new RouteData();
    routeData.Values.Add("CountryCode", "DE");            

    // Act
    var result = (OkObjectResult)unitUnderTest.Get(120);

    // Assert
    Assert.True(result.StatusCode == StatusCodes.Status200OK);
}

创建Controller的方法

private MicroMarketPriceOverviewController CreateMicroMarketPriceOverviewController()
{
    var context = new DefaultHttpContext();
    
    context.Request.Headers.Add("X-Correlation-ID", Guid.NewGuid().ToString());
    
    var controllerContext = new ControllerContext()
    {
        HttpContext = context.HttpContext
    };

    context.HttpContext.Request.Path = PathString.FromUriComponent(new Uri("http://localhost/api/v1/{CountryCode}/[controller]"));
    context.HttpContext.Request.RouteValues = new Microsoft.AspNetCore.Routing.RouteValueDictionary() { { "CountryCode", "NL" } };
    return new MicroMarketPriceOverviewController(MockMicroMarketPriceOverviewBusiness.Object, mockLogger);
}

问题原因及解决方案

核心问题:Controller的RouteData未被正确关联

Controller的RouteData属性属于ControllerContext的一部分,当前代码存在两个关键错误:

  • 在CreateMicroMarketPriceOverviewController方法中,创建了controllerContext但未赋值给新建的Controller实例;
  • 测试方法中创建的routeData对象,没有关联到Controller的ControllerContext中。

修正方案

方案一:创建Controller时直接设置RouteData

修改CreateMicroMarketPriceOverviewController方法,将RouteData关联到ControllerContext并赋值给Controller:

private MicroMarketPriceOverviewController CreateMicroMarketPriceOverviewController()
{
    var context = new DefaultHttpContext();
    context.Request.Headers.Add("X-Correlation-ID", Guid.NewGuid().ToString());
    
    // 初始化RouteData并添加参数
    var routeData = new RouteData();
    routeData.Values.Add("CountryCode", "NL");
    
    // 配置ControllerContext并关联RouteData
    var controllerContext = new ControllerContext()
    {
        HttpContext = context.HttpContext,
        RouteData = routeData
    };

    // 创建Controller并赋值ControllerContext
    var controller = new MicroMarketPriceOverviewController(MockMicroMarketPriceOverviewBusiness.Object, mockLogger);
    controller.ControllerContext = controllerContext;
    
    return controller;
}

方案二:测试方法中动态设置RouteData

如果需要在测试中灵活切换CountryCode,可以直接修改Controller的ControllerContext.RouteData:

[Fact]
public void Get_OK()
{
    // Arrange
    MockMicroMarketPriceOverviewBusiness.Setup(x => x.GetMicroMarketOverviewReport(It.IsAny<long>(),It.IsAny<string>()))
        .Returns(Task.Run(() => new MicroMarketOverviewModel()));
    var unitUnderTest = CreateMicroMarketPriceOverviewController();

    // 初始化RouteData并关联到Controller
    var routeData = new RouteData();
    routeData.Values.Add("CountryCode", "DE");
    unitUnderTest.ControllerContext.RouteData = routeData;            

    // Act
    var result = (OkObjectResult)unitUnderTest.Get(120);

    // Assert
    Assert.True(result.StatusCode == StatusCodes.Status200OK);
}

同时需确保CreateMicroMarketPriceOverviewController方法初始化Controller的ControllerContext:

private MicroMarketPriceOverviewController CreateMicroMarketPriceOverviewController()
{
    var context = new DefaultHttpContext();
    context.Request.Headers.Add("X-Correlation-ID", Guid.NewGuid().ToString());
    
    var controller = new MicroMarketPriceOverviewController(MockMicroMarketPriceOverviewBusiness.Object, mockLogger);
    // 初始化ControllerContext
    controller.ControllerContext = new ControllerContext()
    {
        HttpContext = context.HttpContext
    };

    return controller;
}

额外优化:避免同步调用异步方法

Action中使用.GetAwaiter().GetResult()同步调用异步方法可能引发死锁,建议改为异步版本:

public async Task<IActionResult> Get(long siteId)
{
    var countryCode = (string)this.RouteData.Values["CountryCode"];
    var microMarketReport = await _microMarketPriceOverviewBusiness.GetMicroMarketOverviewReport(siteId, countryCode);
    return Ok(microMarketReport);
}

对应的测试方法同步修改:

[Fact]
public async Task Get_OK()
{
    // Arrange
    MockMicroMarketPriceOverviewBusiness.Setup(x => x.GetMicroMarketOverviewReport(It.IsAny<long>(),It.IsAny<string>()))
        .ReturnsAsync(new MicroMarketOverviewModel());
    var unitUnderTest = CreateMicroMarketPriceOverviewController();

    var routeData = new RouteData();
    routeData.Values.Add("CountryCode", "DE");
    unitUnderTest.ControllerContext.RouteData = routeData;            

    // Act
    var result = (OkObjectResult)await unitUnderTest.Get(120);

    // Assert
    Assert.Equal(StatusCodes.Status200OK, result.StatusCode);
}

内容的提问来源于stack exchange,提问作者Abhishek_Singh_Rana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 14:45:24