重载受保护的泛型方法单元测试导致AmbiguousMatchException

时间:2013-12-20 13:18:01

标签: c# unit-testing moq

我试图在我的代码重载签名中测试(使用Moq)重载的受保护泛型方法:

protected void AutoMap<FromType, ToType>(IList<FromType> sources
                                         , IList<ToType> destinations)

protected void AutoMap<FromType, ToType>(FromType from
                                         , ToType to)

为了能够访问这些方法中的第二个,我使用的是PrivateObject,然后通过反射调用方法:

a obj_a = new a();
b obj_b = new b();
PrivateObject o = new PrivateObject(controller);
o.Invoke("AutoMap"
         , new Type[] { typeof(a), typeof(b) }
         , new Object[] { obj_a, obj_b }
         , new Type[] { typeof(a), typeof(b) });

当我运行测试时,我得到System.Reflection.AmbiguousMatchException,表示发现了不明确的匹配。

任何人都可以告诉我如何正确地做到这一点? 干杯 ķ。

2 个答案:

答案 0 :(得分:2)

通常,您不应该测试受保护的方法。只应测试公共方法。从单元测试的角度来看,受保护的方法应该被视为私有。

您应该测试您的实现类,并看到它按预期工作。如果这个类使用受保护/私有方法来完成它的工作 - 这是实现细节,并且实现可以随时改变而不改变类的行为(想想重构)。

假设你有:

public abstract class Base
{
   public virtual int GetResult(int data)
   {
       var z = GetMoreData();
       return data*z;
   }
   protected int GetMoreData()
   {
      ///something
      return something;
    }
}

public class MyRealImplementation : Base
{
    public override int GetResult(int data)
    {
       //wahtever
    }
}

没有办法(或没有意义)测试受保护的方法。我们不知道被覆盖的GetResult会使用它(它可能会,也可能不会)。

唯一想测试的是GetResult是否返回预期结果,而不是它是如何做到的。

在特定情况下,如果你在接口后面抽象映射功能会更好,所以你可以模拟它。

public interface IMapper
{
    AutoMap<FromType, ToType>(IList<FromType> sources, IList<ToType> destinations);
    oid AutoMap<FromType, ToType>(FromType from, ToType to);
}

在您的PrivateObject中注入该接口并使用它而不是受保护的方法。有了这个,你会:

  1. 让您的课程/方法遵循单一责任原则
  2. 独立测试映射
  3. 使用模拟测试您的私人课程
  4. 一般情况下 - 每次编写单元测试都很困难,通常这意味着类/方法做得太多。

答案 1 :(得分:1)

虽然我同意Sunny的answer关于测试受保护方法的愚蠢行为,但这个问题让我很感兴趣,所以我试着搞清楚这里出了什么问题。

问题是PrivateObject类的限制,它基本上只是一些反射方法的包装器。这是一个已知限制,请参阅示例问题herehere

如果你真的想测试这样的方法,你需要自己使用这些反射方法。

//this is ugly, but the quickest way I could filter down to the overload that 
//doesn't take in the IList interface arguments
var method = typeof (YOUR_CONTROLLER_CLASS).GetMethods(
             BindingFlags.Instance | BindingFlags.NonPublic)
            .First(m => m.IsGenericMethod 
                && m.Name == "AutoMap" 
                && !m.GetParameters()[0].ParameterType.IsInterface);
var genericMethod = method.MakeGenericMethod(new[] {typeof (a), typeof (b)});
genericMethod.Invoke(controller, new object[]{ obj_a, obj_b });

希望有所帮助。