Come si usa Moq per simulare un metodo di estensione?


87

Sto scrivendo un test che dipende dai risultati di un metodo di estensione, ma non voglio che un futuro fallimento di quel metodo di estensione rompa mai questo test. Deridere quel risultato sembrava la scelta ovvia, ma Moq non sembra offrire un modo per ignorare un metodo statico (un requisito per un metodo di estensione). C'è un'idea simile con Moq.Protected e Moq.Stub, ma non sembrano offrire nulla per questo scenario. Mi sto perdendo qualcosa o dovrei affrontarlo in un modo diverso?

Ecco un esempio banale che fallisce con la solita "Aspettativa non valida su un membro non sovrascrivibile" . Questo è un cattivo esempio di necessità di deridere un metodo di estensione, ma dovrebbe funzionare.

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

Per quanto riguarda i drogati di TypeMock che potrebbero suggerire di usare Isolator invece: apprezzo lo sforzo poiché sembra che TypeMock potrebbe fare il lavoro bendato e inebriato, ma il nostro budget non aumenterà presto.


3
Un duplicato può essere trovato qui: stackoverflow.com/questions/2295960/… .
Oliver

6
Questa domanda è più vecchia di un anno intero. Se c'è una duplicazione, va dall'altra parte.
patridge

2
Ancora nessuna soluzione adeguata nel 2019!
TanvirArjel

1
@TanvirArjel In realtà, da molti anni potresti usare JustMock per simulare i metodi di estensione. Ed è semplice come deridere qualsiasi altro metodo. Ecco un collegamento alla documentazione: Extension Methods Mocking
Mihail Vladov

Risposte:


71

I metodi di estensione sono solo metodi statici mascherati. Framework mocking come Moq o Rhinomocks possono solo creare istanze fittizie di oggetti, questo significa che non è possibile deridere metodi statici.


70
@ Mendelt .. Allora come testerebbe un'unità un metodo che internamente ha un metodo di estensione? quali sono le possibili alternative?
Sai Avinash

2
@Alexander Ho avuto la stessa domanda e poi questa ha risposto in modo fantastico: agooddayforscience.blogspot.com/2017/08/… -Dai un'occhiata a questo, è un salvavita!
letie

31

Se puoi modificare il codice dei metodi di estensione, puoi codificarlo in questo modo per poter testare:

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!";
    }
}

Quindi i metodi di estensione sono solo un involucro attorno all'interfaccia di implementazione.

(È possibile utilizzare solo la classe di implementazione senza metodi di estensione che sono una sorta di zucchero sintattico.)

E puoi deridere l'interfaccia di implementazione e impostarla come implementazione per la classe delle estensioni.

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


15

Ho creato una classe wrapper per i metodi di estensione che dovevo simulare.

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

Quindi puoi deridere i metodi wrapper nei tuoi test e codice con il tuo contenitore IOC.


Questa è una soluzione alternativa in cui non è più possibile utilizzare la sintassi dei metodi di estensione nel codice. Ma aiuta quando non è possibile modificare la classe dei metodi di estensione.
informatorius

@informatorius Cosa intendi dire? Nel tuo codice usi MyExtension () dalla classe MyExtensions. Nei tuoi test, utilizzi MyExtension () dalla classe ExtensionMethodsWrapper () che fornisce il parametro.
ranthonissen

Se la mia classe in prova utilizza MyExtension () da MyExtensions, non posso deridere MyExtensions. Quindi la classe sottoposta a test deve utilizzare IExtensionMethodsWrapper in modo che possa essere derisa. Ma poi la classe sotto test non può più utilizzare la sintassi del metodo di estensione.
informatorius

1
Questo è il punto centrale dell'OP. Questa è una soluzione alternativa.
ranthonissen

4

Per i metodi di estensione normalmente utilizzo il seguente approccio:

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

Permette di iniettare _doSumm abbastanza facilmente.


2
L'ho appena provato ma ci sono problemi quando si passa l'oggetto Parent deriso per cui l'estensione è quando lo si passa in un altro metodo che si trova in un altro assembly. In tal caso, anche con questa tecnica, l'estensione originale viene scelta quando il metodo viene chiamato nell'altro assembly, una sorta di problema di scoping. Se chiamo direttamente nel progetto Unit Test va bene, ma quando stai testando altro codice che chiama il metodo di estensione semplicemente non aiuta.
Stephen York

@Stephen Non so come evitarlo. Non ho mai avuto questo tipo di problema di scoping
dmigo

0

La cosa migliore che puoi fare è fornire un'implementazione personalizzata per il tipo che ha il metodo di estensione, ad esempio:

[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;
    }
}
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.