如何用Moq.Of<T>语法实现带回调与参数捕获的接口Mock配置
使用Moq的Mock.Of语法重构Mock配置
背景接口与传统测试代码
我们有如下接口:
public interface IGetRandomString { void Init(); bool IsInited { get; } // 返回指定长度的随机字符串 string GetRandomString(int length); }
以及采用传统Moq写法的测试代码:
public void GetRandomStringTest() { bool initialized = false; Random random = new Random(); string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-._"; Mock<IGetRandomString> mock; IGetRandomString mockedInterface; // Mock配置 mock = new Mock<IGetRandomString>(); mockedInterface = mock.Object; mock.Setup(d => d.Init()).Callback(() => initialized = true); mock.Setup(d => d.IsInited).Returns(() => initialized); mock.Setup(d => d.GetRandomString(It.IsAny<int>())).Returns<int>(length => initialized ? GetRandomString(length) : string.Empty); // 单元测试逻辑 const int length = 10; mockedInterface.Init(); var isInited = mockedInterface.IsInited; var ret = mockedInterface.GetRandomString(length); Assert.That(ret.Length == length); } private string GetRandomString(int length) => new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
问题
如何使用现代的Mock.Of<T>语法实现上述相同的Mock配置?目前已写出部分代码,需要补充完成Init方法的回调设置以及GetRandomString方法的参数捕获逻辑:
... mockedInterface = Mock.Of<IGetRandomString>(m => // 属性配置已完成 m.IsInited == initialized && // 如何配置无返回值方法的回调? m.Init() && // 如何捕获方法的length参数? m.GetRandomString(It.IsAny<int>()) == (initialized ? GetRandomString(length) : string.Empty)); mock = Mock.Get(mockedInterface); ...
解决方案
Mock.Of<T>的表达式语法主要用于配置返回值,但对于无返回值的方法(如Init)的回调,需要先通过Mock.Of<T>创建实例,再通过Mock.Get()获取Mock对象补充配置。完整实现如下:
public void GetRandomStringTestWithMockOf() { bool initialized = false; Random random = new Random(); string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-._"; // 使用Mock.Of<T>创建实例,先配置属性和基础方法匹配 var mockedInterface = Mock.Of<IGetRandomString>(m => m.IsInited == initialized && m.GetRandomString(It.IsAny<int>()) == string.Empty ); // 获取Mock对象补充回调和动态返回逻辑 var mock = Mock.Get(mockedInterface); // 配置Init方法的回调逻辑 mock.Setup(m => m.Init()) .Callback(() => initialized = true); // 配置GetRandomString的动态返回,捕获length参数 mock.Setup(m => m.GetRandomString(It.IsAny<int>())) .Returns<int>(length => initialized ? GetRandomString(length) : string.Empty); // 单元测试逻辑与原代码一致 const int length = 10; mockedInterface.Init(); var isInited = mockedInterface.IsInited; var ret = mockedInterface.GetRandomString(length); Assert.That(ret.Length == length); } private string GetRandomString(int length) => new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
关键说明
- 无返回值方法的回调:
Mock.Of<T>的表达式无法直接配置void方法的回调,必须通过Mock.Get()拿到Mock实例后,用传统的Setup().Callback()方式配置。 - 方法参数捕获:对于需要根据参数动态返回值的方法,建议在
Mock.Of<T>中先做基础匹配,再通过Setup().Returns<T>捕获参数并处理返回逻辑,这种方式更清晰且符合Moq的设计逻辑。 - 属性动态关联:原代码中
IsInited与initialized变量的动态关联,在Mock.Of<T>的表达式m.IsInited == initialized中会自动生效,后续initialized变量变化时,属性返回值会同步更新。
内容的提问来源于stack exchange,提问作者bairog
相关产品推荐
相关产品推荐

