如何Mock INodeQuery.GetNodes()使任意表达式返回固定Fixture值?
问题:Mock INodeQuery.GetNodes()返回固定Fixture值失败
我需要对NodeProcessService进行Mock测试,已定义以下接口与类:
public interface INode { public int Id { get; set; } public string NodeName { get; set; } public IEnumerable<INode> Childrens { get; set; } } public interface INodeQuery { public IEnumerable<INode> GetNodes(); } public class NodeProcessService { readonly INodeQuery nodeQuery; public NodeProcessService(INodeQuery nodeQuery) { this.nodeQuery = nodeQuery; } public string currentNodeName { get;set; } public int Handler() { var getNode = nodeQuery.GetNodes() .Select(p => p.Childrens.FirstOrDefault(c => c.NodeName == currentNodeName)) .FirstOrDefault(p => p != null); //do something... } }
测试时希望Mock INodeQuery,让GetNodes()无论后续使用何种表达式,都返回单个固定的Fixture值,但以下两种Setup写法均报错:
nodeQuery.Setup(s => s.GetNodes().FirstOrDefault(It.IsAny<Func<INode, bool>>())).Returns(fakeNode)
或
nodeQuery.Setup(s => s.GetNodes().FirstOrDefault()).Returns(fakeNode)
测试代码结构如下:
public NodeServiceTests() { fixture.Customize(new AutoMoqCustomization()); nodeQuery = fixture.Freeze<Mock<INodeQuery>>(); sut = fixture.Create<NodeProcessService>(); } [Fact] public void SimpleTest() { var fakeNode = fixture.Create<INode>(); nodeQuery.Setup(s => s.GetNodes().FirstOrDefault(It.IsAny<Func<INode, bool>>())).Returns(fakeNode); var result = sut.Handler(); }
解决方案
错误原因
你试图Mock的FirstOrDefault是Linq扩展方法,并非INodeQuery接口定义的方法,Moq无法直接Mock链式调用中的扩展方法。正确的做法是直接MockINodeQuery.GetNodes()方法,让它返回一个符合业务代码Linq筛选条件的集合,从而确保后续操作能得到你想要的固定节点。
正确实现方式
根据业务代码逻辑,Handler()会遍历GetNodes()返回的节点的子节点,找到名称匹配currentNodeName的节点。我们只需要构造对应的集合并MockGetNodes()返回它即可:
[Fact] public void SimpleTest() { // 1. 创建要返回的固定子节点,并设置匹配的名称 var targetChildNode = fixture.Create<INode>(); var targetNodeName = fixture.Create<string>(); targetChildNode.NodeName = targetNodeName; sut.currentNodeName = targetNodeName; // 2. 创建父节点,将固定子节点加入其Childrens集合 var parentNode = fixture.Create<INode>(); parentNode.Childrens = new List<INode> { targetChildNode }; // 3. Mock GetNodes()返回包含该父节点的集合 nodeQuery.Setup(s => s.GetNodes()).Returns(new List<INode> { parentNode }); var result = sut.Handler(); // 此处添加你的断言逻辑,比如验证result是否符合预期 }
简化场景(强制返回固定节点)
如果希望完全忽略业务代码的筛选逻辑,直接让Handler()拿到固定节点,可以构造一个让Linq操作必然返回该节点的集合:
[Fact] public void SimpleTest() { var fakeNode = fixture.Create<INode>(); // 设置currentNodeName匹配fakeNode的名称 sut.currentNodeName = fakeNode.NodeName; // 创建一个父节点,其Childrens直接包含fakeNode var parentNode = fixture.Build<INode>() .With(n => n.Childrens, new List<INode> { fakeNode }) .Create(); nodeQuery.Setup(s => s.GetNodes()).Returns(new List<INode> { parentNode }); var result = sut.Handler(); }
内容的提问来源于stack exchange,提问作者Emily
相关产品推荐
相关产品推荐

