用Moq模拟扩展方法

Rus*_*ngs 153 c# extension-methods unit-testing moq mocking

我有一个预先存在的界面......

public interface ISomeInterface
{
    void SomeMethod();
}
Run Code Online (Sandbox Code Playgroud)

并且我使用mixin扩展了这个表面...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一个叫这个我要测试的课程...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}
Run Code Online (Sandbox Code Playgroud)

和测试,我想模拟界面并验证对扩展方法的调用...

    [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();
    }
Run Code Online (Sandbox Code Playgroud)

然而,运行此测试会产生异常......

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()
Run Code Online (Sandbox Code Playgroud)

我的问题是,有一个很好的方法来模拟混合调用吗?

Dan*_*sco 30

你不能用模拟框架"直接"模拟静态方法(因此扩展方法).您可以尝试Moles(http://research.microsoft.com/en-us/projects/pex/downloads.aspx),这是Microsoft提供的一种免费工具,可以实现不同的方法.以下是该工具的说明:

Moles是.NET中基于委托的测试存根和绕道的轻量级框架.

Moles可用于绕过任何.NET方法,包括密封类型中的非虚拟/静态方法.

您可以将Moles与任何测试框架一起使用(它与此无关).

  • 理论上的痣是好的,但是当我试验它时,我发现了三个问题阻止了我使用它... 1)它没有在Resharper NUnit转轮中运行2)你需要为每个短柱组件手动创建一个鼹鼠组件3 )每当存根方法改变时,您需要手动重新创建一个鼹鼠组件. (6认同)
  • 除了Moles之外,还有其他(非自由)模拟框架使用.NET的分析器API来模拟对象,因此可以替换任何调用.我知道的两个是[Telerik的JustMock](http://www.telerik.com/products/mocking.aspx)和[TypeMock Isolator](http://www.typemock.com/typemock-isolator-product3). (2认同)

Alv*_*vis 18

我用过Wrapper来解决这个问题.创建一个包装器对象并传递您的模拟方法.

请参阅Paul Irwin的" 用于单元测试模拟静态方法",它有很好的例子.

  • 我喜欢这个答案,因为它所说的(没有直接说出来)是你需要改变你的代码以使其可测试.这就是它的工作原理.了解在微芯片/ IC/ASIC设计中,这些芯片不仅要设计成可以工作,还要设计得更加可测试,因为如果你不能测试微芯片,它就没用了 - 你不能保证它会工作.软件也是如此.如果你还没有把它建成可测试的,它就是......没用.构建它是可测试的,在某些情况下意味着重写代码(和使用包装器),然后构建测试它的自动化测试. (8认同)
  • 我创建了一个包含 Dapper、Dapper.Contrib 和 IDbConnection 的小型库。https://github.com/codeapologist/DataAbstractions.Dapper (4认同)

chr*_*389 10

我发现我必须发现我试图模拟输入的扩展方法的内部,并模拟扩展内部的内容.

我查看使用扩展名直接将代码添加到您的方法中.这意味着我不需要模拟扩展内部发生的事情而不是扩展本身.

  • 这并不总是可能的。例如,当使用非开源扩展时,您没有编写。 (2认同)

小智 7

如果您只想确保调用了扩展方法,并且不尝试设置返回值,那么您可以检查Invocations模拟对象上的属性。

像这样:

var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);
Run Code Online (Sandbox Code Playgroud)


Rhy*_*ous 6

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

我使用 Action、Func、Predicate 或 delegate 类型的内部惰性可注入属性,并允许在单元测试期间注入(交换)方法。

    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;
Run Code Online (Sandbox Code Playgroud)

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

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

有关更完整的示例,请查看http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


pas*_*asx 5

You can mock a test interface that inherits from the real one and has a member with the same signature as the extension method.

You can then mock the test interface, add the real one to the mock and call the test method in the setup.

Your implementation of the mock can then call whatever method you want or simply check the that the method is called:

IReal //on which some extension method is defined
{
    ... SomeNotAnExtensionMethod(...);
}

ITest: IReal
{
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod()).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeNotAnExtensionMethod()).Verifiable(); //Calls SomeNotAnExtensionMethod on IReal
Run Code Online (Sandbox Code Playgroud)

Thanks to Håvard S solution in this post for how to implement a mock that supports to interface. Once I found it, adapting it with the test interface and the static method was a cake walk.