使用 Moq 模拟扩展方法

发布于 2024-08-22 08:08:40 字数 1253 浏览 16 评论 0原文

我有一个预先存在的接口...

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

我的问题是:有没有一种很好的方法来模拟 mixin 调用?

I have a pre-existing Interface...

public interface ISomeInterface
{
    void SomeMethod();
}

and I've extended this interface using a mixin...

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

I have a class thats calling this which I want to test...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

and a test where I'd like to mock the interface and verify the call to the extension method...

    [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();
    }

Running this test however generates an exception...

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

My question is: Is there a nice way to mock out the mixin call?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

说好的呢 2024-08-29 08:08:40

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

请参阅模拟单元测试的静态方法作者:Paul Irwin,其中有很好的例子。

I have used a Wrapper to get around this problem. Create a wrapper object and pass your mocked method.

See Mocking Static Methods for Unit Testing by Paul Irwin, it has nice examples.

甜宝宝 2024-08-29 08:08:40

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

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

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

您可以将 Moles 与任何测试框架一起使用(它是独立的)。

You can't "directly" mock static method (hence extension method) with mocking framework. You can try Moles (http://research.microsoft.com/en-us/projects/pex/downloads.aspx), a free tool from Microsoft that implements a different approach.
Here is the description of the tool:

Moles is a lightweight framework for test stubs and detours in .NET that is based on delegates.

Moles may be used to detour any .NET method, including non-virtual/static methods in sealed types.

You can use Moles with any testing framework (it's independent about that).

云柯 2024-08-29 08:08:40

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

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

I found that I had to discover the inside of the extension method I was trying to mock the input for, and mock what was going on inside the extension.

I viewed using an extension as adding code directly to your method. This meant I needed to mock what happens inside the extension rather than the extension itself.

千年*琉璃梦 2024-08-29 08:08:40

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

像这样:

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

If you just want to make sure that the extension method was invoked, and you aren't trying to setup a return value, then you can check the Invocations property on the mocked object.

Like this:

var invocationsCount = mockedObject.Invocations.Count;
invocationsCount.Should().BeGreaterThan(0);
流殇 2024-08-29 08:08:40

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

我使用 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;

然后您调用 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/

I like to use the wrapper (adapter pattern) when I am wrapping the object itself. I'm not sure I'd use that for wrapping an extension method, which is not part of the object.

I use an internal Lazy Injectable Property of either type Action, Func, Predicate, or delegate and allow for injecting (swapping out) the method during a unit test.

    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;

Then you call the Func instead of the actual method.

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

For a more complete example, check out http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

淡写薰衣草的香 2024-08-29 08:08:40

好的答案中已经给出了无法模拟扩展方法的原因。我只是想用这个答案给出另一个可能的解决方案:通过调用扩展方法提取受保护的虚拟方法,并使用代理在测试类/方法中创建此方法的设置。

public class Foo
{
  public void Method()
    => CallToStaticMethod();

  protected virtual void CallToStaticMethod()
    => StaticClass.StaticMethod();
}

并测试

[TestMethod]
    public void MyTestMethod()
    {
        var expected = new Exception("container exception");
        var proxy = new Mock<Foo>();
        proxy.Protected().Setup("CallToStaticMethod").Throws(expected);

        var actual = Assert.ThrowsException<Exception>(() => proxy.Object.Foo());

        Assert.AreEqual(expected, actual);
    }
  

Reason why it is not possible to mock an extension method is already given in good answers. I am just trying to give another possible solution with this answer: Extract a protected, virtual method with the call to the extension method and create a setup for this method in the test class/method by using a proxy.

public class Foo
{
  public void Method()
    => CallToStaticMethod();

  protected virtual void CallToStaticMethod()
    => StaticClass.StaticMethod();
}

and test

[TestMethod]
    public void MyTestMethod()
    {
        var expected = new Exception("container exception");
        var proxy = new Mock<Foo>();
        proxy.Protected().Setup("CallToStaticMethod").Throws(expected);

        var actual = Assert.ThrowsException<Exception>(() => proxy.Object.Foo());

        Assert.AreEqual(expected, actual);
    }
  
灼痛 2024-08-29 08:08:40

就我而言,扩展方法是围绕我的类的某些公共方法的方法。所以我检查了该内部方法的调用。这种方法类似于阿尔维斯的回答(上面)。

In my case extension method is a method around some public method of my class. So I checked call of that internal method. That approach is similar to Alvis answer (above).

腻橙味 2024-08-29 08:08:40

因此,如果您使用 Moq,并且想要模拟扩展方法的结果,那么您可以在具有扩展名的模拟类的实例上使用 SetupReturnsDefault(new ConcreteInstanceToReturn())您试图模拟的方法。

它并不完美,但对于单元测试来说它工作得很好。

So if you are using Moq, and want to mock the result of an Extension method, then you can use SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) on the instance of the mock class that has the extension method you are trying to mock.

It is not perfect, but for unit testing purposes it works well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文