使用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

