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

대신 Isolator를 사용하도록 제안 할 수있는 TypeMock 중독자에 대해서는 TypeMock이 눈을 가리고 술에 취해 작업을 수행 할 수있는 것처럼 보이지만 우리의 예산은 곧 증가하지 않습니다.


3
중복 파일은 stackoverflow.com/questions/2295960/… 에서 찾을 수 있습니다 .
올리버

6
이 질문은 그 질문보다 1 년 더 오래되었습니다. 중복이 있으면 반대 방향으로 이동합니다.
patridge 2010 년

2
2019 년에는 여전히 적절한 해결책이 없습니다!
TanvirArjel

1
@TanvirArjel 실제로 수년 동안 JustMock 을 사용 하여 확장 방법을 모의 할 수있었습니다 . 그리고 다른 방법을 조롱하는 것처럼 간단합니다. 다음은 문서 링크입니다. Extension Methods Mocking
Mihail Vladov

답변:


69

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


68
@Mendelt .. 그러면 한 단위가 내부적으로 확장 메서드가있는 메서드를 어떻게 테스트할까요? 가능한 대안은 무엇입니까?
Sai Avinash

@Mendelt, 한 단위가 내부적으로 확장 메서드가있는 메서드를 어떻게 테스트할까요? 가능한 대안은 무엇입니까?
Alexander

@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

이것은 과소 평가 된 의견입니다.
Raj


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


이는 코드에서 더 이상 확장 메서드 구문을 사용할 수없는 해결 방법입니다. 그러나 확장 메서드 클래스를 변경할 수 없을 때 도움이됩니다.
informatorius

@informatorius 그게 무슨 뜻이야? 코드에서 MyExtensions 클래스의 MyExtension ()을 사용합니다. 테스트에서 매개 변수를 제공하는 ExtensionMethodsWrapper () 클래스의 MyExtension ()을 사용합니다.
ranthonissen

테스트중인 클래스가 MyExtensions의 MyExtension ()을 사용하는 경우 MyExtensions를 모의 할 수 없습니다. 따라서 테스트중인 클래스는 모의 될 수 있도록 IExtensionMethodsWrapper를 사용해야합니다. 그러나 테스트중인 클래스는 더 이상 확장 메서드 구문을 사용할 수 없습니다.
informatorius

1
이것이 OP의 요점입니다. 이것이 해결 방법입니다.
ranthonissen apr

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
방금 시도했지만 확장이 다른 어셈블리에있는 다른 메서드로 전달할 때 부모 모의 개체를 전달할 때 문제가 있습니다. 이 기술을 사용하더라도 다른 어셈블리에서 메서드가 호출 될 때 원래 확장이 선택됩니다. 일종의 범위 문제입니다. 단위 테스트 프로젝트에서 직접 호출해도 괜찮지 만 확장 메서드를 호출하는 다른 코드를 테스트 할 때는 도움이되지 않습니다.
Stephen York

@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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.