2010-02-19 105 views
102

我有一个预先存在的接口嘲讽扩展方法...与起订量

public interface ISomeInterface 
{ 
    void SomeMethod(); 
} 

,我已经使用一个混合扩展这个intreface ...

public static class SomeInterfaceExtensions 
{ 
    public static void AnotherMethod(this ISomeInterface someInterface) 
    { 
     // Implementation here 
    } 
} 

我有一个类多数民众赞成在调用此我想测试...

public class Caller 
{ 
    private readonly ISomeInterface someInterface; 

    public Caller(ISomeInterface someInterface) 
    { 
     this.someInterface = someInterface; 
    } 

    public void Main() 
    { 
     someInterface.AnotherMethod(); 
    } 
} 

和一个测试,我想模拟接口和验证扩展方法的调用...

[Test] 
    public void Main_BasicCall_CallsAnotherMethod() 
    { 
     // Arrange 
     var someInterfaceMock = new Mock<ISomeInterface>(); 
     someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable(); 

     var caller = new Caller(someInterfaceMock.Object); 

     // Act 
     caller.Main(); 

     // Assert 
     someInterfaceMock.Verify(); 
    } 

但是运行这个测试生成异常...

System.ArgumentException: Invalid setup on a non-member method: 
x => x.AnotherMethod() 

我的问题是,有没有嘲笑了混入通话的好办法?

+2

根据我的经验,术语mixin和扩展方法是分开的东西。在这种情况下,我会使用后者来避免混淆:P – 2010-02-19 12:33:49

+2

重复:http://stackoverflow.com/questions/562129/how-do-i-use-moq-to-mock-an-extension-method。 – Oliver 2012-10-12 13:05:24

回答

18

你不能用模拟框架“直接”模拟静态方法(因此扩展方法)。您可以尝试使用Moles免费工具(http://research.microsoft.com/en-us/projects/pex/downloads.aspx),该工具采用了不同的方法。 下面是工具的描述:

痣是在.NET测试存根和弯路是基于委托一个轻量级的框架。

痣可能用于绕道任何.NET方法,包括密封类型的非虚拟/静态方法。

你可以在任何测试框架(它是独立的)使用莫尔斯。

+2

除Moles之外,还有其他(非免费)模拟框架使用.NET的Profiler API来模拟对象,因此可以替换任何调用。我知道的两个人是[Telerik的JustMock](http://www.telerik.com/products/mocking.aspx)和[TypeMock Isolator](http://www.typemock.com/typemock-isolator-product3)。 – 2011-08-24 04:34:02

+3

痣在理论上是好的,但我发现了三个问题,当我试用它时,使我停止使用它...... 1)它不在Resharper NUnit亚军中运行2)您需要为每个残肢组件手动创建一个鼹鼠装配3)每当存根方法改变时,您都需要手动重新创建一个摩尔程序集。 – 2011-08-26 13:41:18

10

我已经使用Wrapper来解决这个问题。创建一个包装对象并传递你的模拟方法。

请参阅Mocking Static Methods for Unit Testing Paul Irwin,它有很好的例子。

+0

我喜欢这个答案,因为它的意思是说(不直接说)它是你需要改变你的代码,使其可测试。这就是它的工作原理。理解在微芯片/ IC/ASIC设计中,这些芯片不仅要设计成可工作的,而且要设计得更加可测试,因为如果你不能测试芯片,它就没用 - 你不能保证它会工作。软件也一样。如果你还没有建立它是可测试的,那么......没用。将它构建为可测试的,在某些情况下意味着重写代码(并使用包装器),然后构建测试它的自动化测试。 – 2017-12-22 04:35:46

1

我喜欢在包装对象本身时使用包装器(适配器模式)。我不确定我会用它来包装一个扩展方法,它不是对象的一部分。

我使用Action,Func,Predicate或delegate类型的内部Lazy Injectable Property,并允许在单元测试期间注入(交换)该方法。

internal Func<IMyObject, string, object> DoWorkMethod 
    { 
     [ExcludeFromCodeCoverage] 
     get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); } 
     set { _DoWorkMethod = value; } 
    } private Func<IMyObject, string, object> _DoWorkMethod; 

然后你调用Func而不是实际的方法。

public object SomeFunction() 
    { 
     var val = "doesn't matter for this example"; 
     return DoWorkMethod.Invoke(MyObjectProperty, val); 
    } 

对于一个更完整的示例,请http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

+0

这很好,但读者应该知道_DoWorkMethod是类的新字段,该类的每个实例现在必须分配一个字段。这很重要,但有时它取决于您在任何时候分配的实例的数量。您可以通过将_DoWorkMethod设置为静态来解决此问题。这样做的缺点是,如果你有单元测试同时运行,那么可能会修改相同静态值的两个不同的单元测试。 – zumalifeguard 2016-11-02 01:07:52

3

我发现,我已经发现的扩展方法我试图嘲弄为输入的内部,并模拟了事情的原委内延期。

我看过的扩展是静态的,就像直接将代码放在方法中一样,所以您需要模拟扩展内部发生的情况而不是扩展本身。