Moq를 이용한 모의 확장 방법


174

기존 인터페이스가 있습니다 ...

public interface ISomeInterface
{
    void SomeMethod();
}

그리고 mixin을 사용 하여이 intreface를 확장했습니다 ...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

테스트하고 싶은 클래스가 있습니다 ...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

인터페이스를 조롱하고 확장 메소드에 대한 호출을 확인하려는 테스트 ...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

그러나이 테스트를 실행하면 예외가 발생합니다 ...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

내 질문은, 믹스 인 호출을 조롱하는 좋은 방법이 있습니까?


3
내 경험상 mixin과 extension 방법이라는 용어는 별개의 것입니다. 이 경우 후자를 사용하여 혼합을 피할 수 있습니다. : P
Ruben Bartelink

답변:


33

조롱 프레임 워크로 정적 메소드 (따라서 확장 메소드)를 "직접"조롱 할 수 없습니다. 다른 접근 방식을 구현하는 Microsoft의 무료 도구 인 Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx )를 사용해 볼 수 있습니다 . 다음은 도구에 대한 설명입니다.

Moles는 델리게이트를 기반으로하는 .NET의 테스트 스텁 및 우회 용 경량 프레임 워크입니다.

봉인 된 유형의 비가 상 / 정적 방법을 포함하여 .NET 방법을 우회하는 데 두더지를 사용할 수 있습니다.

테스트 프레임 워크와 함께 Moles를 사용할 수 있습니다 (독립적 임).


2
Moles 외에도 .NET의 프로파일 러 API를 사용하여 객체를 조롱하여 모든 호출을 대체 할 수있는 다른 (무료가 아닌) 조롱 프레임 워크가 있습니다. 내가 아는 두 가지는 Telerik의 JustMockTypeMock Isolator 입니다.
Marcel Gosselin

6
이론적으로 두더지가 좋지만 시험 사용해 보았을 때 세 가지 문제를 발견했습니다 .1) Resharper NUnit 러너에서 실행되지 않습니다. ) 스텁 된 방법이 변경 될 때마다 두더지 조립품을 수동으로 다시 작성해야합니다.
러셀 Giddings

26

이 문제를 해결하기 위해 래퍼를 사용했습니다. 랩퍼 오브젝트를 작성하고 조롱 된 메소드를 전달하십시오.

Paul Irwin의 단위 테스트위한 정적 메소드 조롱을 참조하십시오 . 좋은 예가 있습니다.


12
나는 (직접 말하지 않고) 테스트 할 수 있도록 코드를 변경해야하기 때문에이 답변을 좋아합니다. 그것이 작동하는 방식입니다. 마이크로 칩 / IC / ASIC 설계에서 이러한 칩은 작동하도록 설계 될뿐만 아니라 테스트가 가능하도록 더 설계되어야한다는 점을 이해하십시오. 마이크로 칩을 테스트 할 수 없으면 쓸모가 없으므로 작업. 소프트웨어도 마찬가지입니다. 테스트 할 수 있도록 빌드하지 않은 경우 ... 쓸모가 없습니다. 테스트 가능하도록 빌드하십시오. 경우에 따라 코드를 다시 작성하고 랩퍼를 사용하는 것을 의미하며 테스트하는 자동화 된 테스트를 빌드하십시오.
Michael Plautz

2
Dapper, Dapper.Contrib 및 IDbConnection을 래핑하는 작은 라이브러리를 만들었습니다. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido

15

입력을 조롱하려고하는 확장 방법의 내부를 발견하고 확장 내부에서 진행중인 것을 조롱해야한다는 것을 알았습니다.

확장 프로그램을 사용하여 메소드에 직접 코드를 추가하는 것으로 보았습니다. 이것은 확장 자체가 아닌 확장 내부에서 일어나는 일을 조롱해야 함을 의미했습니다.


11

실제 인터페이스를 상속하고 확장 메소드와 동일한 서명을 가진 멤버가있는 테스트 인터페이스를 모의 할 수 있습니다.

그런 다음 테스트 인터페이스를 모의하고 실제 인터페이스를 모의에 추가하고 설정에서 테스트 메소드를 호출 할 수 있습니다.

모의 구현은 원하는 메소드를 호출하거나 메소드가 호출되었는지 간단히 확인할 수 있습니다.

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

이 게시물의 Håvard S 솔루션 덕분에 두 개의 인터페이스를 지원하는 모의 객체 를 구현하는 방법 있습니다. 일단 그것을 발견하면 테스트 인터페이스와 정적 방법으로 조정하면 케이크 산책이었습니다.


당신은 우리가 동일한 샘플 서명을 기쁘게 보여줄 수 SomeNotAnExtensionMethodSomeNotAnExtensionMethod? 이제 인터페이스 내에서 확장 메소드 서명을 작성하는 방법을 알고 있습니다.
Peter Csala

1
@Peter Csala : 이것이 확실하지 않다면 죄송합니다. 게시물을 더 명확하게하기 위해 업데이트하고 SomeNotAnExtensionMethod의 이름을 SomeRegularMethod로 변경했습니다.
pasx

iReal매개 변수를 어떻게 전달합니까SomeExtensionMethod 경우ITest 합니까? 첫 번째 매개 변수로 전달하면 어떻게 설정합니까? Setup( x=> x.SomeExtensionMethod(x, ...)이로 인해 런타임 예외가 발생합니다.
피터 Csala

당신은 그것을 통과하지 않습니다. 모의 관점에서 ITest는 코드의 확장 메소드에 대한 호출의 서명을 일치시키기 위해이 매개 변수가없는 일반 메소드를 포함하므로 여기에서 IReal을 수신하지 않으며 IReal / ITest의 구현은 모의 자체입니다. . SomeExtensionMethod에서 조롱 된 IReal의 일부 속성에 액세스하려면 다음과 같이 조롱에서 모든 작업을 수행해야합니다. object _mockCache = whatever...`Setup (x => x.SomeExtensionMethod (...) .. Callback (() => access _mockCache here);)
pasx

8

JustMock으로 확장 방법을 쉽게 조롱 할 수 있습니다 . API는 일반 방법을 조롱하는 것과 같습니다. 다음을 고려하세요

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

이 방법을 정렬하고 확인하려면 다음을 사용하십시오.

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

다음은 또한 문서에 대한 링크입니다. Extension Methods Mocking


2

객체 자체를 래핑 할 때 래퍼 (어댑터 패턴)를 사용하고 싶습니다. 객체의 일부가 아닌 확장 메서드를 래핑하는 데 사용할 것인지 잘 모르겠습니다.

Action, Func, Predicate 또는 delegate 유형의 내부 Lazy Injectable Property을 사용하고 단위 테스트 중에 메소드를 주입 ​​(스왑 아웃) 할 수 있습니다.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

그런 다음 실제 방법 대신 Func을 호출합니다.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

더 완전한 예를 보려면 http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/를 확인 하십시오.


이것은 좋지만 독자는 _DoWorkMethod가 클래스의 각 인스턴스가 이제 하나 이상의 필드를 할당해야하는 클래스의 새 필드라는 것을 알고 있어야합니다. 중요하지 않은 경우도 있지만 한 번에 할당하는 인스턴스 수에 따라 경우에 따라 다릅니다. _DoWorkMethod를 정적으로 만들어이 문제를 해결할 수 있습니다. 이에 대한 단점은 단위 테스트를 동시에 실행하는 경우 동일한 정적 값을 수정할 수있는 서로 다른 두 가지 단위 테스트가 완료된다는 것입니다.
zumalifeguard

-1

따라서 Moq를 사용하고 있으며 확장 메소드의 결과를 조롱하려는 경우 사용할 수 있습니다 SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) 가있는 Mock 클래스의 인스턴스에서 .

완벽하지는 않지만 단위 테스트 목적으로 잘 작동합니다.


나는 그것이 SetReturnsDefault <T> ()라고 믿는다
David

구체적인 인스턴스를 반환하지 않습니다. 커스텀 c # 클래스의 경우 null입니다!
HelloWorld
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.