[c#] Moq를 사용하여 확장 방법을 모의하려면 어떻게해야합니까?

확장 메서드의 결과에 따라 달라지는 테스트를 작성 중이지만 해당 확장 메서드의 향후 실패로 인해이 테스트가 중단되는 것을 원하지 않습니다. 그 결과를 조롱하는 것이 당연한 선택으로 보였지만 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이 눈을 가리고 술에 취해 작업을 수행 할 수있는 것처럼 보이지만 우리의 예산은 곧 증가하지 않습니다.



답변

확장 메서드는 변장 된 정적 메서드 일뿐입니다. Moq 또는 Rhinomocks와 같은 모의 프레임 워크는 개체의 모의 인스턴스 만 생성 할 수 있습니다. 즉, 정적 메서드를 모의 할 수 없습니다.


답변

확장 메서드 코드를 변경할 수 있다면 다음과 같이 코딩하여 테스트 할 수 있습니다.

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


답변

나는이 질문이 약 1 년 동안 활성화되지 않았다는 것을 알고 있지만 마이크로 소프트는 이것을 Moles 라고하는 정확하게 처리하기위한 프레임 워크를 출시했습니다 .

다음은 몇 가지 자습서입니다.

  • DimeCasts.net
  • Nikolai Tillman의 튜토리얼


  • 답변

    모의 작업에 필요한 확장 메서드에 대한 래퍼 클래스를 만들었습니다.

    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 컨테이너를 사용하여 테스트 및 코드에서 래퍼 메서드를 모의 할 수 있습니다.


    답변

    확장 방법의 경우 일반적으로 다음 접근 방식을 사용합니다.

    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을 매우 쉽게 주입 할 수 있습니다.


    답변

    가장 좋은 방법은 확장 메서드가있는 유형에 대해 사용자 정의 구현을 제공하는 것입니다. 예 :

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


    답변