¿Cómo uso Moq para simular un método de extensión?

87

Estoy escribiendo una prueba que depende de los resultados de un método de extensión, pero no quiero que una falla futura de ese método de extensión rompa esta prueba. Burlarse de ese resultado parecía la opción obvia, pero Moq no parece ofrecer una forma de anular un método estático (un requisito para un método de extensión). Existe una idea similar con Moq.Protected y Moq.Stub, pero no parecen ofrecer nada para este escenario. ¿Me estoy perdiendo algo o debería hacerlo de una manera diferente?

A continuación se muestra un ejemplo trivial que falla con la "expectativa no válida en un miembro no reemplazable" habitual . Este es un mal ejemplo de la necesidad de simular un método de extensión, pero debería funcionar.

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

En cuanto a los adictos a TypeMock que podrían sugerir que use Isolator en su lugar: agradezco el esfuerzo ya que parece que TypeMock podría hacer el trabajo con los ojos vendados y ebrio, pero nuestro presupuesto no aumentará pronto.

patridge
fuente
3
Puede encontrar un duplicado aquí: stackoverflow.com/questions/2295960/… .
Oliver
6
Esta pregunta es un año más antigua que esa. Si hay duplicación, es al revés.
Patridge
2
¡Todavía no hay una solución adecuada en 2019!
TanvirArjel
1
@TanvirArjel En realidad, desde hace muchos años puedes usar JustMock para simular métodos de extensión. Y es tan simple como burlarse de cualquier otro método. Aquí hay un enlace a la documentación: Métodos de extensión
Burlarse

Respuestas:

69

Los métodos de extensión son solo métodos estáticos disfrazados. Los frameworks simulados como Moq o Rhinomocks solo pueden crear instancias simuladas de objetos, esto significa que no es posible simular métodos estáticos.

Mendelt
fuente
68
@ Mendelt ... Entonces, ¿cómo probaría una unidad un método que internamente tiene un método de extensión? ¿Cuáles son las posibles alternativas?
Sai Avinash
@Mendelt, ¿cómo probaría una unidad un método que internamente tiene un método de extensión? ¿Cuáles son las posibles alternativas?
Alexander
@Alexander Tenía la misma pregunta y luego esto la respondió fantásticamente: agooddayforscience.blogspot.com/2017/08/… -Mira esto, ¡es un salvavidas!
LTV
30

Si puede cambiar el código de los métodos de extensión, puede codificarlo así para poder probar:

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

Entonces, los métodos de extensión son solo un envoltorio alrededor de la interfaz de implementación.

(Podría usar solo la clase de implementación sin métodos de extensión que son una especie de azúcar sintáctico).

Y puede simular la interfaz de implementación y configurarla como implementación para la clase de extensiones.

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));
    }
}
informatorius
fuente
Muchas gracias por esto. Fue extremadamente útil y me ayudó a superar algunos obstáculos en las pruebas.
DerHaifisch
Este es un comentario subestimado.
Raj
16

Sé que esta pregunta no ha estado activa durante aproximadamente un año, pero Microsoft lanzó un marco para manejar exactamente esto llamado Moles .

Aquí también hay algunos tutoriales:

  • DimeCasts.net
  • Tutorial de Nikolai Tillman

  • Mike Fielden
    fuente
    15

    Creé una clase contenedora para los métodos de extensión que necesitaba simular.

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

    Luego, puede simular los métodos de envoltura en sus pruebas y codificar con su contenedor IOC.

    Ranthonissen
    fuente
    Esta es una solución en la que ya no puede usar la sintaxis de los métodos de extensión en su código. Pero ayuda cuando no puede cambiar la clase de métodos de extensión.
    informatorius
    @informatorius ¿Qué quieres decir con eso? En su código, usa MyExtension () de la clase MyExtensions. En sus pruebas, usa MyExtension () de la clase ExtensionMethodsWrapper () que proporciona el parámetro.
    ranthonissen
    Si mi clase bajo prueba usa MyExtension () de MyExtensions, entonces no puedo burlarme de MyExtensions. Entonces, la clase bajo prueba debe usar IExtensionMethodsWrapper para que pueda ser burlada. Pero entonces la clase bajo prueba ya no puede usar la sintaxis del método de extensión.
    informatorius
    1
    Ese es el objetivo del OP. Esta es una solución para eso.
    ranthonissen
    4

    Para los métodos de extensión, normalmente uso el siguiente enfoque:

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

    Permite inyectar _doSumm bastante fácil.

    dmigo
    fuente
    2
    Intenté esto, pero hay problemas al pasar el objeto simulado principal para el que es la extensión al pasarlo a algún otro método que está en otro ensamblado. En ese caso, incluso con esta técnica, la extensión original se elige cuando se llama al método en el otro ensamblado, una especie de problema de alcance. Sin embargo, si llamo directamente en el proyecto de prueba unitaria, está bien, pero cuando está probando otro código que llama al método de extensión, simplemente no ayuda.
    Stephen York
    @ Stephen No estoy seguro de cómo evitar esto. Nunca tuve este tipo de problema de alcance
    dmigo
    0

    Lo mejor que puede hacer es proporcionar una implementación personalizada para el tipo que tiene el método de extensión, por ejemplo:

    [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;
        }
    }
    
    Ross
    fuente