95

我正在编写一个依赖于扩展方法结果的测试,但我不希望该扩展方法未来的失败会破坏这个测试。模拟该结果似乎是显而易见的选择,但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 });

至于任何可能建议我使用 Isolator 的 TypeMock 爱好者:我很欣赏你的努力,因为看起来 TypeMock 可以蒙着眼睛和醉酒地完成这项工作,但我们的预算不会很快增加。

4

6 回答 6

77

扩展方法只是变相的静态方法。Moq 或 Rhinomocks 等模拟框架只能创建对象的模拟实例,这意味着模拟静态方法是不可能的。

于 2009-02-18T18:02:25.757 回答
36

如果您可以更改扩展方法代码,那么您可以像这样编写代码以便能够测试:

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));
    }
}
于 2014-03-28T18:04:03.177 回答
16

我知道这个问题已经有一年没有活跃了,但微软发布了一个框架来处理这个问题,叫做Moles

这里还有一些教程:

  • DimeCasts.net
  • Nikolai Tillman 的教程

  • 于 2010-04-28T20:35:57.927 回答
    15

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

    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 容器模拟测试和代码中的包装器方法。

    于 2010-10-01T14:30:00.583 回答
    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 相当容易。

    于 2015-01-29T16:23:49.207 回答
    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;
        }
    }
    
    于 2020-06-17T05:38:04.520 回答