Deridere i metodi di estensione con Moq


175

Ho un'interfaccia preesistente ...

public interface ISomeInterface
{
    void SomeMethod();
}

e ho esteso questa intreface usando un mixin ...

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

Ho una lezione che si chiama così e che voglio testare ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

e un test in cui mi piacerebbe deridere l'interfaccia e verificare la chiamata al metodo di estensione ...

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

L'esecuzione di questo test genera tuttavia un'eccezione ...

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

La mia domanda è: c'è un modo carino per deridere la chiamata del mixin?


3
Nella mia esperienza, i termini metodi di mixin ed estensione sono cose separate. Utilizzerei quest'ultimo in questo caso per evitare confusioni: P
Ruben Bartelink,

Risposte:


33

Non è possibile "direttamente" deridere il metodo statico (quindi il metodo di estensione) con il framework di derisione. Puoi provare Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), uno strumento gratuito di Microsoft che implementa un approccio diverso. Ecco la descrizione dello strumento:

Moles è un framework leggero per stub di test e deviazioni in .NET basato su delegati.

Le talpe possono essere utilizzate per deviare qualsiasi metodo .NET, inclusi metodi non virtuali / statici in tipi sigillati.

Puoi utilizzare Moles con qualsiasi framework di test (è indipendente al riguardo).


2
Oltre a Moles, ci sono altri framework di derisione (non gratuiti) che utilizzano l'API di profiler di .NET per deridere oggetti e quindi possono sostituire qualsiasi chiamata. I due che conosco sono JustMock e TypeMock Isolator di Telerik .
Marcel Gosselin,

6
La talpa in teoria è buona, ma quando l'ho provata ho riscontrato tre problemi che mi hanno impedito di usarla ... 1) Non funziona nel corridore Resharper NUnit 2) È necessario creare manualmente un assieme di talpe per ogni assieme stub 3 ) È necessario ricreare manualmente un assieme talpa ogni volta che cambia un metodo stubbing.
Russell Giddings,

26

Ho usato un wrapper per aggirare questo problema. Crea un oggetto wrapper e passa il tuo metodo deriso.

Vedi Metodi statici beffardi per i test unitari di Paul Irwin, ha dei buoni esempi.


12
Mi piace questa risposta perché ciò che sta dicendo (senza dirlo direttamente) è che è necessario modificare il codice per renderlo testabile. Funziona così. Tieni presente che nella progettazione di microchip / IC / ASIC, questi chip non devono solo essere progettati per funzionare, ma progettati anche ulteriormente per essere testabili, perché se non riesci a testare un microchip, è inutile - non puoi garantire che lo farà lavoro. Lo stesso vale per il software. Se non l'hai costruito per essere testabile, è ... inutile. Costruiscilo per essere testabile, che in alcuni casi significa riscrivere il codice (e usare i wrapper), quindi costruisci i test automatici che lo testano.
Michael Plautz,

2
Ho creato una piccola libreria che avvolge Dapper, Dapper.Contrib e IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido

15

Ho scoperto che dovevo scoprire l'interno del metodo di estensione per cui stavo cercando di deridere l'input e deridere cosa stava succedendo all'interno dell'estensione.

Ho visto l'utilizzo di un'estensione come aggiunta di codice direttamente al tuo metodo. Ciò significava che dovevo deridere ciò che accade all'interno dell'estensione anziché l'estensione stessa.


11

Puoi deridere un'interfaccia di test che eredita da quella reale e ha un membro con la stessa firma del metodo di estensione.

È quindi possibile prendere in giro l'interfaccia di test, aggiungere quella reale alla simulazione e chiamare il metodo di prova nell'impostazione.

L'implementazione del mock può quindi chiamare qualunque metodo tu voglia o semplicemente controllare che il metodo sia chiamato:

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

Grazie alla soluzione Håvard S in questo post per come implementare una simulazione che supporta due interfacce. Una volta l'ho trovato, adattandolo con l'interfaccia di test e il metodo statico è stata una passeggiata.


Puoi mostrarci per favore la stessa firma di esempio per SomeNotAnExtensionMethode SomeNotAnExtensionMethod? Ora ho idea di come creare una firma del metodo di estensione all'interno di un'interfaccia ...
Peter Csala

1
@Peter Csala: scusami se non è chiaro. Ho aggiornato il post per renderlo più chiaro e ribattezzato SomeNotAnExtensionMethod in SomeRegularMethod.
pasx

Come si passa il iRealparametro a SomeExtensionMethodin caso di ITest? Se lo si passa come primo parametro, come si configura? Setup( x=> x.SomeExtensionMethod(x, ...)questo causerà un'eccezione di runtime.
Peter Csala,

Non lo passi. Dal punto di vista del mock ITest contiene un metodo regolare senza questo parametro per abbinare la firma delle chiamate al metodo di estensione nel tuo codice in modo da non ricevere mai un IReal qui e in ogni caso l'implementazione di IReal / ITest è il mock stesso . Se si desidera accedere ad alcune proprietà dell'IReal deriso in SomeExtensionMethod, si dovrebbe fare tutto ciò nella simulazione, ad es .: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => è possibile accedi a _mockCache qui);)
pasx

8

Puoi facilmente deridere un metodo di estensione con JustMock . L'API è la stessa del metodo normale beffardo. Considera quanto segue

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

Per organizzare e verificare questo metodo, utilizzare quanto segue:

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

Ecco anche un link alla documentazione: Metodi di estensione Derisione


2

Mi piace usare il wrapper (modello adattatore) quando sto avvolgendo l'oggetto stesso. Non sono sicuro che lo userei per avvolgere un metodo di estensione, che non fa parte dell'oggetto.

Uso una proprietà iniettabile pigra interna di tipo Action, Func, Predicate o delegato e consento l'iniezione (sostituzione) del metodo durante un test unitario.

    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;

Quindi si chiama Func anziché il metodo effettivo.

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

Per un esempio più completo, consulta http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


Questo va bene, ma i lettori dovrebbero essere consapevoli del fatto che _DoWorkMethod è un nuovo campo della classe che ogni istanza della classe ora deve allocare un altro campo. È raro che questo sia importante, ma a volte lo fa a seconda del numero di istanze che stai allocando in qualsiasi momento. È possibile aggirare questo problema rendendo statico _DoWorkMethod. Il rovescio della medaglia è che se si hanno contemporaneamente test unitari, si completeranno due test unitari diversi che possono modificare lo stesso valore statico.
zumalifeguard

-1

Quindi se stai usando Moq e vuoi deridere il risultato di un metodo Extension, allora puoi usare SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())l'istanza della classe mock che ha il metodo di estensione che stai cercando di deridere.

Non è perfetto, ma per scopi di test unitari funziona bene.


Credo che sia SetReturnsDefault <T> ()
David

che non restituisce mai l'istanza concreta. Solo null in caso di una classe c # personalizzata!
HelloWorld,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.