用Moq模拟扩展方法

时间:2010-02-19 11:41:05

标签: c# unit-testing mocking extension-methods moq

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

public interface ISomeInterface
{
    void SomeMethod();
}

我已经使用mixin扩展了这个...

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()

我的问题是,有没有一种很好的方法来模拟混音调用?

8 个答案:

答案 0 :(得分:30)

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

  

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

     

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

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

答案 1 :(得分:16)

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

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

答案 2 :(得分:6)

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

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

答案 3 :(得分:6)

您可以轻松地使用JustMock模拟扩展方法。该API与模拟正常方法相同。考虑以下

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

要安排和验证此方法,请使用以下内容:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

这里也是文档的链接:Extension Methods Mocking

免责声明。我是负责JustMock的开发人员之一。

答案 4 :(得分:2)

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

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

    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/

答案 5 :(得分:2)

您可以模拟一个测试接口,该接口继承自真实接口,并且具有与扩展方法具有相同签名的成员。

然后您可以模拟测试界面,将真实的界面添加到模拟界面中,然后在设置中调用测试方法。

然后,您的模拟实现可以调用您想要的任何方法,或者只是检查该方法是否被调用:

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

感谢this post中的HåvardS解决方案如何实现支持接口的模拟。一旦找到它,就可以通过测试界面和静态方法对其进行调整了。

答案 6 :(得分:1)

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

像这样:

var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);

答案 7 :(得分:-1)

因此,如果您正在使用Moq,并且想模拟Extension方法的结果,则可以在具有您要模拟的扩展方法的模拟类的实例上使用SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())

它不是完美的,但是对于单元测试而言,它运行良好。