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

使用xUnit测试.NET Core控制器时Mock方法返回Null的问题

问题分析

你遇到的问题根源在于:Moq 在 mock 类时,会默认将所有虚方法(包括从父类 ControllerBase 继承来的 Ok() 方法)设置为返回默认值(null)。

你的 Post 方法最后调用 Ok(test) 返回结果,而 Ok(object) 是 ControllerBase 中的虚方法。当你使用 Mock<TestController> 创建控制器的 mock 对象时,Moq 自动重写了这个 Ok 方法并让它返回 null,所以即使你正确 mock 了受保护的 GetString 方法,最终整个 Post 方法的返回结果也会是 null。

解决方案

这里提供两种可行的解决思路,你可以根据自己的需求选择:

方案1:创建控制器子类重写受保护方法(更简洁)

不需要用 Moq mock 整个控制器,而是直接创建 TestController 的子类,在子类中重写 GetString 方法来返回测试预期值。这种方式更直观,也避免了 Moq 对父类虚方法的默认影响。

示例代码:

public class TestControllerTest {
    private Mock<ITestService> MockTestService { get; }

    public TestControllerTest() {
        MockTestService = new Mock<ITestService>();
    }

    // 创建测试用的控制器子类
    private class TestableTestController : TestController {
        public string MockedGetStringResult { get; set; }

        public TestableTestController(ITestService testService) : base(testService) { }

        protected override string GetString(string testString) {
            return MockedGetStringResult;
        }
    }

    [Fact]
    public void Post_TakesTestString_ReturnsString() {
        // 初始化测试子类
        var testController = new TestableTestController(MockTestService.Object) {
            MockedGetStringResult = "testMockValue"
        };

        // 调用Post方法
        var result = testController.Post(new TestModel()).Result;

        // 验证结果
        var okResult = Assert.IsType<OkObjectResult>(result);
        Assert.Equal("testMockValue", okResult.Value);
    }
}

方案2:配置 Moq 使用 CallBase 或显式 Setup Ok 方法

如果你坚持要用 Moq mock 控制器,可以通过两种方式解决:

方式A:启用 CallBase 属性

让 Moq 对于没有显式 Setup 的虚方法,调用基类的原始实现。这样 Ok() 方法就会正常返回 OkObjectResult,而不是 null。

修改测试代码如下:

[Fact]
public void Post_TakesTestString_ReturnsString() {
    var MockController = new Mock<TestController>(MockTestService.Object) {
        // 启用CallBase,让未Setup的虚方法调用基类实现
        CallBase = true
    };

    MockController.Protected()
                  .Setup<string>("GetString", ItExpr.IsAny<string>())
                  .Returns("testMockValue")
                  .Verifiable();

    var result = MockController.Object.Post(new TestModel()).Result;

    // 验证结果类型和值
    var okResult = Assert.IsType<OkObjectResult>(result);
    Assert.Equal("testMockValue", okResult.Value);

    MockController.Protected().Verify("GetString", Times.Once(), ItExpr.IsAny<string>());
}

方式B:显式 Setup Ok 方法

直接告诉 Moq 当调用 Ok(object) 时,返回正确的 OkObjectResult:

[Fact]
public void Post_TakesTestString_ReturnsString() {
    var MockController = new Mock<TestController>(MockTestService.Object);

    // Setup受保护的GetString方法
    MockController.Protected()
                  .Setup<string>("GetString", ItExpr.IsAny<string>())
                  .Returns("testMockValue")
                  .Verifiable();

    // Setup Ok方法,返回预期的OkObjectResult
    MockController.Setup(c => c.Ok(It.IsAny<object>()))
                  .Returns<object>(value => new OkObjectResult(value));

    var result = MockController.Object.Post(new TestModel()).Result;

    var okResult = Assert.IsType<OkObjectResult>(result);
    Assert.Equal("testMockValue", okResult.Value);

    MockController.Protected().Verify("GetString", Times.Once(), ItExpr.IsAny<string>());
}
总结

个人更推荐方案1,因为它更简洁,不需要处理 Moq 对父类虚方法的默认行为,单元测试的逻辑也更清晰。如果一定要使用 Moq,方案2的方式A(启用 CallBase)是更省心的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:07:53