访问字典中的Mock Assembly对象时抛出KeyNotFoundException问题排查
问题分析与解决
问题重现
测试代码中使用Moq模拟Assembly类,将模拟对象存入Dictionary<Assembly, HashSet<Type>>后,明明通过Assert.Contains确认键存在,但访问字典值时抛出KeyNotFoundException,甚至调用TryGetValue也返回false。
测试代码:
[Fact] public void MockAssembly_GetTypes_ReturnsMockedTypes() { var mockAssembly = new Mock<Assembly>(); mockAssembly.Setup(a => a.GetTypes()).Returns(new Type[] { typeof(MyClass) }); var mockAssemblyObject = mockAssembly.Object; var assemblyDictionary = new Dictionary<Assembly, HashSet<Type>>(); if (!assemblyDictionary.TryGetValue(mockAssemblyObject, out var types)) { types = new HashSet<Type>(mockAssemblyObject.GetTypes()); assemblyDictionary[mockAssemblyObject] = types; } Assert.Contains(mockAssemblyObject, assemblyDictionary.Keys); Assert.Single(assemblyDictionary[mockAssemblyObject]); }
异常信息:
System.Collections.Generic.KeyNotFoundException: The given key '' was not present in the dictionary.
原因
Assembly继承自MarshalByRefObject,这类对象的实例本质是透明代理。Moq生成的Assembly模拟对象,其默认的Equals和GetHashCode方法行为不符合字典的键匹配要求:
- 存入字典时使用的是代理对象的初始哈希码,后续访问时
GetHashCode返回值可能发生变化; Equals方法无法正确识别同一个代理实例,导致字典判定键不存在。
仅MockGetHashCode不足以解决问题——字典同时依赖GetHashCode和Equals来确定键的唯一性。
解决方法
方案1:Mock Equals和GetHashCode方法
手动模拟这两个方法,让它们基于引用相等判断:
[Fact] public void MockAssembly_GetTypes_ReturnsMockedTypes() { var mockAssembly = new Mock<Assembly>(); var mockAssemblyObject = mockAssembly.Object; // 模拟GetTypes方法 mockAssembly.Setup(a => a.GetTypes()).Returns(new Type[] { typeof(MyClass) }); // 模拟Equals:基于引用比较 mockAssembly.Setup(a => a.Equals(It.IsAny<object>())).Returns((object other) => ReferenceEquals(mockAssemblyObject, other)); // 模拟GetHashCode:返回代理对象的原始哈希码 mockAssembly.Setup(a => a.GetHashCode()).Returns(mockAssemblyObject.GetHashCode()); var assemblyDictionary = new Dictionary<Assembly, HashSet<Type>>(); if (!assemblyDictionary.TryGetValue(mockAssemblyObject, out var types)) { types = new HashSet<Type>(mockAssemblyObject.GetTypes()); assemblyDictionary[mockAssemblyObject] = types; } Assert.Contains(mockAssemblyObject, assemblyDictionary.Keys); Assert.Single(assemblyDictionary[mockAssemblyObject]); }
方案2:使用引用比较器
创建字典时传入基于引用比较的IEqualityComparer,无需Mock对象方法:
// .NET Core 2.1+/NET Framework 4.7.2+ 可用内置类型 var assemblyDictionary = new Dictionary<Assembly, HashSet<Type>>(ReferenceEqualityComparer.Instance);
低版本.NET可自行实现引用比较器:
public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class { public bool Equals(T x, T y) => ReferenceEquals(x, y); public int GetHashCode(T obj) => obj.GetHashCode(); } // 使用方式 var assemblyDictionary = new Dictionary<Assembly, HashSet<Type>>(new ReferenceEqualityComparer<Assembly>());
内容的提问来源于stack exchange,提问作者AlwaysNeedingHelp
相关产品推荐
相关产品推荐

