如何使用Moq模拟扩展方法?


87

我正在编写一个依赖于扩展方法结果的测试,但是我不希望该扩展方法的将来失败会破坏该测试。模拟该结果似乎是显而易见的选择,但是Moq似乎没有提供重写静态方法(扩展方法的要求)的方法。Moq.Protected和Moq.Stub也有类似的想法,但是在这种情况下他们似乎没有提供任何帮助。我是否缺少某些东西,还是应该以其他方式处理?

这是一个简单的示例,但失败了,并出现了常见的“对不可覆盖成员的无效期望”。这是一个需要模拟扩展方法的糟糕示例,但应该这样做。

public class SomeType {
    int Id { get; set; }
}

var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
        .Returns(new SomeType { Id = 5 });

至于可能建议我使用隔离器的所有TypeMock迷:我很感谢所做的努力,因为看起来TypeMock可以蒙住眼睛和使人兴奋,但我们的预算不会很快增加。


3
可以在这里找到副本:stackoverflow.com/questions/2295960/…
奥利弗(Oliver)

6
这个问题比那个问题大一整年。如果存在重复,则相反。
patridge,2012年

2
2019年仍然没有合适的解决方案!
TanvirArjel

1
@TanvirArjel实际上,多年来,您可以使用JustMock模拟扩展方法。和模拟其他任何方法一样简单。这是文档的链接:扩展方法
模拟

Answers:


69

扩展方法只是伪装的静态方法。Moq或Rhinomocks之类的模拟框架只能创建对象的模拟实例,这意味着无法模拟静态方法。


68
@Mendelt ..然后,一个单元将如何测试内部具有扩展方法的方法?有哪些可能的替代方案?
Sai Avinash

@Mendelt,一个单元如何测试内部具有扩展方法的方法?有哪些可能的替代方案?
亚历山大,

@Alexander我有相同的问题,然后这个问题得到了梦幻般的回答:agooddayforscience.blogspot.com/2017/08/…- 看看这个,它可以拯救生命!
LTV

30

如果可以更改扩展方法代码,则可以像这样进行编码以进行测试:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public static class MyExtensions
{
    public static IMyImplementation Implementation = new MyImplementation();

    public static string MyMethod(this object obj)
    {
        return Implementation.MyMethod(obj);
    }
}

public interface IMyImplementation
{
    string MyMethod(object obj);
}

public class MyImplementation : IMyImplementation
{
    public string MyMethod(object obj)
    {
        return "Hello World!";
    }
}

因此,扩展方法只是实现接口的包装。

(您可以只使用实现类,而不能使用类似于语法糖的扩展方法。)

您可以模拟实现接口并将其设置为扩展类的实现。

public class MyClassUsingExtensions
{
    public string ReturnStringForObject(object obj)
    {
        return obj.MyMethod();
    }
}

[TestClass]
public class MyTests
{
    [TestMethod]
    public void MyTest()
    {
        // Given:
        //-------
        var mockMyImplementation = new Mock<IMyImplementation>();

        MyExtensions.Implementation = mockMyImplementation.Object;

        var myClassUsingExtensions = new MyClassUsingExtensions();

        // When:
        //-------
        var myObject = new Object();
        myClassUsingExtensions.ReturnStringForObject(myObject);

        //Then:
        //-------
        // This would fail because you cannot test for the extension method
        //mockMyImplementation.Verify(m => m.MyMethod());

        // This is success because you test for the mocked implementation interface
        mockMyImplementation.Verify(m => m.MyMethod(myObject));
    }
}

非常感谢你。这非常有用,这使我克服了一些测试障碍。
DerHaifisch '18

这是一个被低估的评论。
拉吉


14

我为需要模拟的扩展方法创建了一个包装器类。

public static class MyExtensions
{
    public static string MyExtension<T>(this T obj)
    {
        return "Hello World!";
    }
}

public interface IExtensionMethodsWrapper
{
    string MyExtension<T>(T myObj);
}

public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
{
    public string MyExtension<T>(T myObj)
    {
        return myObj.MyExtension();
    }
}

然后,您可以在测试中模拟包装方法,并使用IOC容器进行编码。


这是一种变通方法,无法在代码中再使用扩展方法语法。但是,当您不能更改扩展方法类时,它会有所帮助。
信息人

@informatorius是什么意思?在您的代码中,使用MyExtensions类中的MyExtension()。在测试中,使用提供参数的ExtensionMethodsWrapper()类中的MyExtension()。
ranthonissen

如果我的被测类使用MyExtensions中的MyExtension(),则无法模拟MyExtensions。因此,要测试的类必须使用IExtensionMethodsWrapper以便可以对其进行模拟。但是随后被测类不能再使用扩展方法语法。
informatorius19年

1
这就是OP的重点。这是一种解决方法。
ranthonissen

4

对于扩展方法,我通常使用以下方法:

public static class MyExtensions
{
    public static Func<int,int, int> _doSumm = (x, y) => x + y;

    public static int Summ(this int x, int y)
    {
        return _doSumm(x, y);
    }
}

它允许相当容易地注入_doSumm。


2
我刚刚尝试过,但是在传递Parent模拟对象时存在问题,该扩展对象是在将其传递到另一个程序集中的其他方法中时要使用的。在那种情况下,即使使用这种技术,当在另一个程序集中调用该方法时,也会选择原始扩展名,这是一个范围问题。如果我直接在单元测试项目中调用,那很好,但是当您测试其他调用扩展方法的代码时,它却无济于事。
斯蒂芬·约克

@Stephen不知道如何避免这种情况。我从来没有遇到过这样的范围问题
dmigo

0

最好的办法是为具有扩展方法的类型提供自定义实现,例如:

[Fact]
public class Tests
{
    public void ShouldRunOk()
    {
        var service = new MyService(new FakeWebHostEnvironment());

        // Service.DoStuff() internally calls the SomeExtensionFunction() on IWebHostEnvironment
        // Here it works just fine as we provide a custom implementation of that interface
        service.DoStuff().Should().NotBeNull();
    }
}

public class FakeWebHostEnvironment : IWebHostEnvironment
{
    /* IWebHostEnvironment implementation */

    public bool SomeExtensionFunction()
    {
        return false;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.