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

我正在编写一个依赖于扩展方法的结果的测试,但是我不希望扩展方法的未来失败会破坏这个测试。嘲笑这个结果似乎是显而易见的选择,但是 Moq 似乎没有提供覆盖静态方法的方法(扩展方法的一个需求)。Moq 也有类似的想法。受保护和莫克。存根,但他们似乎没有为这种情况提供任何东西。是我漏掉了什么,还是我应该换个方式处理这件事?

下面是一个普通的 < em > “对非可覆盖成员的无效期望” 失败的例子。这是一个需要模仿扩展方法的坏例子,但是它应该这样做。

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 的粉丝,他们可能会建议我使用 Isoator 来代替: 我很感激你们的努力,因为 TypeMock 看起来可以蒙着眼睛喝醉酒来完成这项工作,但是我们的预算不会很快增加。

85481 次浏览

Extension methods are just static methods in disguise. Mocking frameworks like Moq or Rhinomocks can only create mock instances of objects, this means mocking static methods is not possible.

I know this question hasn't been active for about a year but Microsoft released a framework to handle exactly this called Moles.

Here are a few tutorials as well:

  • DimeCasts.net
  • Nikolai Tillman's Tutorial

  • I created a wrapper class for the extension methods that I needed to mock.

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

    Then you can mock the wrapper methods in your tests and code with your IOC container.

    If you can change the extension methods code then you can code it like this to be able to test:

    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!";
    }
    }
    

    So the extention methods are only a wrapper around the implementation interface.

    (You could use just the implementation class without extension methods which are sort of syntactic sugar.)

    And you can mock the implementation interface and set it as implementation for the extensions class.

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

    For extension methods I normally use the following approach:

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

    It allows to inject _doSumm fairly easy.

    Best thing you can do is to provide a custom implementation for the type that has the extension method, e.g:

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