使用MsTest与Moq模拟Enumeration隐式运算符及GetAll方法遇空值问题
嘿,我一眼就瞅见问题所在了——你测试里的那个隐式转换完全是画蛇添足,还触发了基类里的一个逻辑漏洞,咱们一步步理清楚:
问题根源分析
先看你的测试代码里的关键行:
var result = (Enumeration)SampleStatus.GetAll<SampleStatus>().Single(x => x.Id == 1).Name;
你已经正确拿到了SampleStatus.Completed实例,取它的Name得到字符串"Completed",然后把这个字符串强制转成Enumeration类型——这一步就是坑!
你的基类Enumeration里的隐式运算符是这么实现的:
public static implicit operator Enumeration(string name) { return GetAll<Enumeration>().FirstOrDefault(i => i.Name == name); }
这里调用的GetAll<Enumeration>()只会查找**Enumeration基类本身**的静态字段,而你的SampleStatus是子类,它的Completed、Deleted都是SampleStatus类型的静态字段,根本不在GetAll<Enumeration>()的查找范围内,所以这一步返回null,自然断言失败。
解决方案
1. 简化测试代码(最快解决测试问题)
你完全不需要把Name转成Enumeration啊!直接对比实例的Name就行,修改后的测试代码:
[TestMethod] public void TestMethod() { // arrange and act var targetStatus = SampleStatus.GetAll<SampleStatus>().Single(x => x.Id == 1); // assert Assert.AreEqual("Completed", targetStatus.Name); // 还可以加个更严谨的断言,直接对比实例 Assert.AreSame(SampleStatus.Completed, targetStatus); }
这样去掉多余的转换,测试直接就能通过,而且逻辑更清晰。
2. 修复基类的字符串转枚举功能(如果确实需要这个能力)
如果你业务中需要把字符串转换成对应的枚举子类实例,那基类的隐式运算符得改——因为它现在只查基类的字段。建议用泛型方法代替隐式运算符,避免基类子类的范围混淆:
public abstract class Enumeration : IComparable { // 保留你原有的其他代码... // 添加泛型的按名称查找方法 public static TEnumeration FromName<TEnumeration>(string name) where TEnumeration : Enumeration { var match = GetAll<TEnumeration>().FirstOrDefault(i => i.Name == name); if (match == null) { throw new ArgumentException($"找不到类型为 {typeof(TEnumeration).Name}、名称为 {name} 的枚举实例"); } return match; } // 如果你非要保留隐式运算符,那可以改成针对子类的,但隐式运算符不能是泛型的,所以更推荐用上面的泛型方法 }
之后需要转换时,直接调用:
var status = SampleStatus.FromName<SampleStatus>("Completed");
这样就能正确拿到SampleStatus.Completed实例,不会返回null。
3. 确认GetAll方法的正确性
顺带说一句,你的GetAll方法是没问题的——它通过typeof(TEnumeration)获取对应类型的静态字段,所以调用GetAll<SampleStatus>()时,能正确拿到Completed和Deleted实例,这部分不用改。
总结
测试失败的核心就是那个多余的隐式转换触发了基类中范围错误的查找逻辑,去掉转换就能快速解决测试问题;如果需要字符串转枚举的功能,用泛型方法代替当前的隐式运算符,能避免基类子类的查找范围混淆问题。
内容的提问来源于stack exchange,提问作者Insomniac

