Moq单元测试如何配置类类型out参数的方法Mock
Moq Mock带out参数方法的解决方案
问题场景
服务层单元测试中需要Mock包含out参数的方法,涉及业务代码如下:
public interface IObj { public bool anotherMethod(string input, out string responseString); } public class SomeClass { public bool SomeMethod(string input, out IObj outputObj) { // 内部业务逻辑 if (logic is correct) { outputObj = // IObj类型实例 return true; } outputObj = null; return false; } } public class Service { public void executingMethod(){ if (this.someClassObj.SomeMethod(this.inputString, out outputObj)) { if (outputObj.anotherMethod(this.anotherInputString, out responseString)) { // 内部业务逻辑 } } } }
使用xUnit+Moq编写测试时,初始写法存在类型不匹配问题,尝试传入mockIObj.Object作为out参数时,又抛出*"属性或索引器不能作为out或ref参数传递"*的错误,相关测试代码如下:
[Fact] public void MockingMethod(){ // Arrange Mock<SomeClass> mockSomeClass = new Mock<SomeClass>(); Mock<IObj> mockIObj = new Mock<IObj>(); string mockedResponse = "someResponse"; // 首次写法问题:out参数传入Mock包装对象而非实际实现实例 mockSomeClass.Setup(s => s.SomeMethod(It.IsAny<string>(), out mockIObj)).Returns(true); mockIObj.Setup(s => s.anotherMethod(It.IsAny<string>(), out mockedResponse)).Returns(true); }
报错的修改写法:
// 直接传入mockIObj.Object触发编译错误 mockSomeClass.Setup(s => s.SomeMethod(It.IsAny<string>(), out mockIObj.Object).Returns(true);
可行解决方案
方案1:提前将Mock实例赋值给本地变量(最简便)
C#要求out/ref参数必须传入可被赋值的变量,不能直接传入属性(mockIObj.Object是Mock类的只读属性,不符合语法要求),只需提前把Mock生成的实际对象存到本地变量再传入即可:
[Fact] public void MockingMethod(){ // Arrange Mock<SomeClass> mockSomeClass = new Mock<SomeClass>(); Mock<IObj> mockIObj = new Mock<IObj>(); string mockedResponse = "someResponse"; // 提前把Mock生成的实例赋值给类型匹配的本地变量 IObj mockedObjInstance = mockIObj.Object; // out参数传入本地变量即可通过编译 mockSomeClass.Setup(s => s.SomeMethod(It.IsAny<string>(), out mockedObjInstance)).Returns(true); mockIObj.Setup(s => s.anotherMethod(It.IsAny<string>(), out mockedResponse)).Returns(true); // 后续实例化Service、注入Mock对象、执行测试断言即可 }
方案2:通过Callback动态设置out返回值(Moq 4.8+版本支持)
如果需要动态控制out参数的返回值,或者不想额外定义本地变量,可以用Moq内置的ref/out参数匹配器,在Returns委托里给out参数赋值:
[Fact] public void MockingMethod(){ // Arrange Mock<SomeClass> mockSomeClass = new Mock<SomeClass>(); Mock<IObj> mockIObj = new Mock<IObj>(); string mockedResponse = "someResponse"; mockSomeClass.Setup(s => s.SomeMethod(It.IsAny<string>(), out It.Ref<IObj>.IsAny)) .Returns((string input, out IObj outputObj) => { outputObj = mockIObj.Object; return true; }); mockIObj.Setup(s => s.anotherMethod(It.IsAny<string>(), out mockedResponse)).Returns(true); }
注意事项
- 如果
SomeClass.SomeMethod不是虚方法,Moq无法直接拦截该方法的执行,建议将SomeClass的抽象抽为接口,通过依赖注入接口的方式实现Mock,否则Mock逻辑不会生效。 - 值类型的out参数(比如示例中的
string responseString)可以直接传入本地值变量,不需要额外包装。
内容的提问来源于stack exchange,提问作者Ritesh Kumar
相关产品推荐
相关产品推荐

