Mockowanie metod rozszerzeń za pomocą Moq

174

Mam już istniejący interfejs ...

public interface ISomeInterface
{
    void SomeMethod();
}

i rozszerzyłem tę intreface za pomocą miksera ...

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

Mam klasę, która to nazywa, którą chcę przetestować ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

i test, w którym chciałbym kpić z interfejsu i zweryfikować wywołanie metody rozszerzenia ...

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

Uruchomienie tego testu generuje jednak wyjątek ...

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

Moje pytanie brzmi: czy istnieje dobry sposób na wyśmiewanie połączenia miksera?

Russell Giddings
źródło
3
Z mojego doświadczenia wynika, że ​​terminy mixin i metody rozszerzające to dwie odrębne rzeczy. W tym przypadku użyłbym tego drugiego, aby uniknąć pomyłek: P
Ruben Bartelink,

Odpowiedzi:

33

Nie można "bezpośrednio" mockować metody statycznej (stąd metody rozszerzającej) przy użyciu frameworka mockującego. Możesz wypróbować Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), bezpłatne narzędzie firmy Microsoft, które wdraża inne podejście. Oto opis narzędzia:

Moles to lekka struktura dla testowych kodów pośredniczących i objazdów w .NET, która jest oparta na delegatach.

Moli można używać do obejścia dowolnej metody .NET, w tym metod niewirtualnych / statycznych w typach zapieczętowanych.

Możesz używać Moles z dowolnym środowiskiem testowym (jest to niezależne od tego).

Daniele Armanasco
źródło
2
Oprócz Moli, istnieją inne (niewolne) frameworki do mockowania, które używają API profilera platformy .NET do mockowania obiektów i mogą zastępować wszelkie wywołania. Dwa, które znam, to JustMock i TypeMock Isolator firmy Telerik .
Marcel Gosselin
6
Krety w teorii są dobre, ale znalazłem trzy problemy, które uniemożliwiły mi ich używanie ... 1) Nie działa w prowadnicy Resharper NUnit 2) Musisz ręcznie utworzyć zespół kretów dla każdego zespołu z końcówką 3 ) Należy ręcznie odtworzyć zespół kreta za każdym razem, gdy zmieni się metoda z króćcem.
Russell Giddings
26

Użyłem Wrappera, aby obejść ten problem. Utwórz obiekt opakowujący i przekaż swoją wyimaginowaną metodę.

Zobacz Mocking Static Methods for Unit Testing autorstwa Paula Irwina, zawiera ładne przykłady.

Alvis
źródło
12
Podoba mi się ta odpowiedź, ponieważ mówi (bez bezpośredniego mówienia), że musisz zmienić swój kod, aby był testowalny. Tak to po prostu działa. Zrozum, że w projektowaniu mikrochipów / IC / ASIC te chipy muszą być nie tylko zaprojektowane do pracy, ale jeszcze bardziej zaprojektowane, aby można je było przetestować, ponieważ jeśli nie możesz przetestować mikroczipa, jest on bezużyteczny - nie możesz zagwarantować, że będzie praca. To samo dotyczy oprogramowania. Jeśli nie zbudowałeś go do testowania, jest ... bezużyteczny. Zbuduj go tak, aby był testowalny, co w niektórych przypadkach oznacza przepisanie kodu (i użycie opakowań), a następnie zbuduj automatyczne testy, które go przetestują.
Michael Plautz
2
Stworzyłem małą bibliotekę, która otacza Dapper, Dapper.Contrib i IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido
15

Odkryłem, że muszę odkryć wnętrze metody rozszerzającej, dla której próbowałem kpić z danych wejściowych, i kpić z tego, co dzieje się wewnątrz rozszerzenia.

Uważam, że użycie rozszerzenia jest dodaniem kodu bezpośrednio do metody. Oznaczało to, że musiałem kpić z tego, co dzieje się wewnątrz rozszerzenia, a nie samo rozszerzenie.

chris31389
źródło
11

Możesz mockować interfejs testowy, który dziedziczy po rzeczywistym i ma element członkowski z tym samym podpisem, co metoda rozszerzenia.

Następnie możesz mockować interfejs testowy, dodać rzeczywisty do makiety i wywołać metodę testową w konfiguracji.

Twoja implementacja makiety może następnie wywołać dowolną metodę lub po prostu sprawdzić, czy wywoływana jest metoda:

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

Dzięki rozwiązaniu Håvard S w tym poście za implementację makiety obsługującej dwa interfejsy. Kiedy już go znalazłem, dostosowanie go za pomocą interfejsu testowego i metody statycznej było jak spacer po torcie.

pasx
źródło
Czy możesz nam pokazać ten sam przykładowy podpis dla SomeNotAnExtensionMethodi SomeNotAnExtensionMethod? Mam teraz pomysł, jak utworzyć podpis metody rozszerzającej w interfejsie ...
Peter Csala
1
@Peter Csala: Przepraszamy, jeśli jest to niejasne. Zaktualizowałem post, aby był bardziej przejrzysty i zmieniłem nazwę SomeNotAnExtensionMethod na SomeRegularMethod.
pasx
Jak przekazujesz iRealparametr do SomeExtensionMethodw przypadku ITest? Jeśli przekażesz go jako pierwszy parametr, to w jaki sposób skonfigurujesz? Setup( x=> x.SomeExtensionMethod(x, ...)spowoduje to wyjątek w czasie wykonywania.
Peter Csala
Nie zdajesz tego. Z punktu widzenia makiety ITest zawiera zwykłą metodę bez tego parametru, aby dopasować sygnaturę wywołań do metody rozszerzenia w kodzie, dzięki czemu nigdy nie otrzymasz tutaj IReal, aw każdym razie implementacja IReal / ITest jest samą makietą . Jeśli chcesz uzyskać dostęp do niektórych właściwości mockowanego IReal w SomeExtensionMethod, powinieneś zrobić to wszystko w makiecie, np .: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => możesz dostęp _mockCache tutaj);)
pasx
8

Możesz łatwo mockować metodę rozszerzenia za pomocą JustMock . API jest tym samym, co mockowanie zwykłej metody. Rozważ następujące

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

Aby zorganizować i zweryfikować tę metodę, użyj następujących:

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

Tutaj jest również link do dokumentacji: Mockowanie metod rozszerzeń

Mihail Vladov
źródło
2

Lubię używać opakowania (wzorca adaptera) podczas zawijania samego obiektu. Nie jestem pewien, czy użyłbym tego do zawijania metody rozszerzenia, która nie jest częścią obiektu.

Używam wewnętrznej właściwości Lazy Injectable typu Action, Func, Predicate lub delegate i pozwalam na wstrzyknięcie (zamianę) metody podczas testu jednostkowego.

    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;

Następnie wywołujesz Func zamiast rzeczywistej metody.

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

Aby uzyskać pełniejszy przykład, odwiedź http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Rhyous
źródło
To dobrze, ale czytelnicy powinni być świadomi, że _DoWorkMethod jest nowym polem klasy, do którego każda instancja klasy musi teraz przydzielić jeszcze jedno pole. Rzadko się to liczy, ale czasami ma to znaczenie w zależności od liczby instancji przydzielanych w dowolnym momencie. Możesz obejść ten problem, ustawiając _DoWorkMethod statyczną. Wadą tego jest to, że jeśli testy jednostkowe są uruchomione jednocześnie, zakończą się dwa różne testy jednostkowe, które mogą modyfikować tę samą wartość statyczną.
zumalifeguard
-1

Więc jeśli używasz Moq i chcesz mockować wynik metody Extension, możesz użyć SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())na wystąpieniu klasy pozorowanej, która ma metodę rozszerzenia, którą próbujesz mockować.

Nie jest doskonały, ale do celów testów jednostkowych działa dobrze.

ckernel
źródło
Uważam, że to SetReturnsDefault <T> ()
David
która nigdy nie zwraca konkretnej instancji. Po prostu null w przypadku niestandardowej klasy C #!
HelloWorld