wake-up-neo.net

Verspottende Erweiterungsmethoden mit Moq

Ich habe ein bereits vorhandenes Interface ...

public interface ISomeInterface
{
    void SomeMethod();
}

und ich habe dieses Interface mit einem Mixin erweitert ...

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

Ich habe eine Klasse, die das nennt und die ich testen möchte ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

und ein Test, bei dem ich die Schnittstelle verspotten und den Aufruf der Erweiterungsmethode verifizieren möchte ...

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

Das Ausführen dieses Tests erzeugt jedoch eine Ausnahme ...

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

Meine Frage ist, gibt es eine nette Weise, den mixin Anruf heraus zu verspotten?

149

Sie können die statische Methode (daher die Erweiterungsmethode) mit dem Spott-Framework nicht "direkt" verspotten. Sie können Moles ( http://research.Microsoft.com/en-us/projects/pex/downloads.aspx ) ausprobieren, ein kostenloses Tool von Microsoft, das einen anderen Ansatz implementiert. Hier ist die Beschreibung des Tools:

Moles ist ein leichtes Framework für Teststubs und Umwege in .NET, das auf Delegierten basiert.

Muttermale können verwendet werden, um eine .NET-Methode zu umgehen, einschließlich nicht-virtueller/statischer Methoden in versiegelten Typen.

Sie können Moles mit jedem Test-Framework verwenden (unabhängig davon).

30

Ich habe einen Wrapper verwendet, um dieses Problem zu umgehen. Erstellen Sie ein Wrapper-Objekt und übergeben Sie Ihre verspottete Methode.

Siehe Mocking Static Methods for Unit Testing von Paul Irwin, es hat schöne Beispiele.

18
Alvis

Ich stellte fest, dass ich das Innere der Erweiterungsmethode herausfinden musste, für die ich die Eingabe verspotten wollte, und verspotten musste, was in der Erweiterung vorging.

Ich habe die Verwendung einer Erweiterung als direktes Hinzufügen von Code zu Ihrer Methode angesehen. Das bedeutete, dass ich mich über das lustig machen musste, was in der Erweiterung passiert, und nicht über die Erweiterung selbst.

9
chris31389

Sie können eine Testschnittstelle verspotten, die von der realen erbt und ein Mitglied mit derselben Signatur wie die Erweiterungsmethode hat.

Anschließend können Sie die Testschnittstelle verspotten, die echte zur Verspottung hinzufügen und die Testmethode im Setup aufrufen.

Ihre Implementierung des Mocks kann dann eine beliebige Methode aufrufen oder einfach überprüfen, ob die Methode aufgerufen wird:

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

ITest: IReal
{
    ... 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.SomeNotAnExtensionMethod()).Verifiable(); //Calls SomeNotAnExtensionMethod on IReal

Dank der Håvard S-Lösung in dieser Beitrag für die Implementierung eines Mocks, der die Schnittstelle unterstützt. Sobald ich es gefunden hatte, war es ein Kinderspiel, es mit der Testschnittstelle und der statischen Methode anzupassen.

3
pasx

Ich verwende gerne den Wrapper (Adaptermuster), wenn ich das Objekt selbst umhülle. Ich bin nicht sicher, ob ich das zum Umbrechen einer Erweiterungsmethode verwenden würde, die nicht Teil des Objekts ist.

Ich verwende eine interne Lazy Injectable-Eigenschaft vom Typ Action, Func, Predicate oder Delegate und erlaube das Injizieren (Austauschen) der Methode während eines Komponententests.

    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;

Dann rufen Sie die Funktion anstelle der eigentlichen Methode auf.

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

Ein vollständigeres Beispiel finden Sie unter http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

2
Rhyous