Métodos de extensión de burla con Moq

175

Tengo una interfaz preexistente ...

public interface ISomeInterface
{
    void SomeMethod();
}

y he extendido esta interfaz usando un mixin ...

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

Tengo una clase que llama esto que quiero probar ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

y una prueba en la que me gustaría simular la interfaz y verificar la llamada al método de extensión ...

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

Sin embargo, ejecutar esta prueba genera una excepción ...

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

Mi pregunta es, ¿hay una buena manera de burlarse de la llamada mixin?

Russell Giddings
fuente
3
En mi experiencia, los términos mixin y métodos de extensión son cosas separadas.
Usaría
3
Duplicado de: stackoverflow.com/questions/562129/… .
Oliver

Respuestas:

33

No se puede "simular" directamente el método estático (de ahí el método de extensión) con el marco de imitación. Puede probar Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), una herramienta gratuita de Microsoft que implementa un enfoque diferente. Aquí está la descripción de la herramienta:

Moles es un marco liviano para trozos de prueba y desvíos en .NET que se basa en delegados.

Los lunares se pueden usar para desviar cualquier método .NET, incluidos los métodos no virtuales / estáticos en tipos sellados.

Puede usar Moles con cualquier marco de prueba (es independiente de eso).

Daniele Armanasco
fuente
2
Además de Moles, hay otros frameworks de simulación (no gratuitos) que utilizan la API de perfilador de .NET para simular objetos y, por lo tanto, pueden reemplazar cualquier llamada. Los dos que conozco son el aislador JustMock y TypeMock de Telerik .
Marcel Gosselin
66
Los lunares en teoría son buenos, pero encontré tres problemas cuando lo probé que me impidieron usarlo ... 1) No se ejecuta en el corredor Resharper NUnit 2) Debe crear manualmente un ensamblaje de mole para cada ensamblaje troquelado 3 ) Debe volver a crear manualmente un ensamblaje de mole cada vez que cambie un método troceado.
Russell Giddings
26

He usado un Wrapper para solucionar este problema. Crea un objeto contenedor y pasa tu método burlado.

Vea Burlándose de métodos estáticos para pruebas unitarias por Paul Irwin, tiene buenos ejemplos.

Alvis
fuente
12
Me gusta esta respuesta porque lo que dice (sin decirlo directamente) es que necesitas alterar tu código para que sea comprobable. Así es como funciona. Comprenda que en el diseño de microchip / IC / ASIC, esos chips no solo deben estar diseñados para funcionar, sino también diseñados para ser comprobables, porque si no puede probar un microchip, es inútil, no puede garantizar que lo haga trabajo. Lo mismo vale para el software. Si no lo ha construido para que sea comprobable, es ... inútil. Compílelo para que sea comprobable, lo que en algunos casos significa reescribir el código (y usar envoltorios), y luego cree las pruebas automatizadas que lo prueban.
Michael Plautz
2
Creé una pequeña biblioteca que envuelve Dapper, Dapper.Contrib e IDbConnection. github.com/codeapologist/DataAbstraction.Dapper
Drew Sumido
15

Descubrí que tenía que descubrir el interior del método de extensión para el que intentaba burlarme de la entrada y burlarme de lo que estaba sucediendo dentro de la extensión.

Lo vi usando una extensión como agregar código directamente a su método. Esto significaba que necesitaba burlarme de lo que sucede dentro de la extensión en lugar de la extensión en sí.

chris31389
fuente
11

Puede simular una interfaz de prueba que hereda de la real y tiene un miembro con la misma firma que el método de extensión.

Luego puede simular la interfaz de prueba, agregar la real a la simulación y llamar al método de prueba en la configuración.

Su implementación del simulacro puede llamar al método que desee o simplemente verificar que se llame al método:

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

Gracias a la solución Håvard S en esta publicación por cómo implementar un simulacro que admite dos interfaces. Una vez que lo encontré, adaptarlo con la interfaz de prueba y el método estático fue muy fácil.

pasx
fuente
¿Puede mostrarnos la misma firma de muestra para SomeNotAnExtensionMethody SomeNotAnExtensionMethod? Ahora tengo idea de cómo crear una firma de método de extensión dentro de una interfaz ...
Peter Csala
1
@ Peter Csala: Lo siento si esto no está claro. Actualicé la publicación para hacerlo más claro y cambié el nombre de SomeNotAnExtensionMethod a SomeRegularMethod.
pasx
¿Cómo se pasa el iRealparámetro a SomeExtensionMethoden caso de ITest? Si lo pasa como primer parámetro, ¿cómo se configura? Setup( x=> x.SomeExtensionMethod(x, ...)Esto provocará una excepción de tiempo de ejecución.
Peter Csala
No lo pasas. Desde el punto de vista del simulacro, ITest contiene un método regular sin este parámetro para hacer coincidir la firma de las llamadas con el método de extensión en su código para que nunca reciba un IReal aquí y, en cualquier caso, la implementación de IReal / ITest es el simulacro mismo . Si desea acceder a algunas propiedades del IReal simulado en SomeExtensionMethod, debe hacer todo eso en el simulacro, por ejemplo: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => puede acceder _mockCache aquí);)
pasx
8

Puede burlarse fácilmente de un método de extensión con JustMock . La API es lo mismo que burlarse del método normal. Considera lo siguiente

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

Para organizar y verificar este método, use lo siguiente:

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

Aquí también hay un enlace a la documentación: Métodos de extensión Burlándose

Mihail Vladov
fuente
2

Me gusta usar el contenedor (patrón de adaptador) cuando estoy envolviendo el objeto en sí. No estoy seguro de usarlo para ajustar un método de extensión, que no es parte del objeto.

Utilizo una propiedad de inyección diferida interna de tipo Action, Func, Predicate o delegate y permito inyectar (intercambiar) el método durante una prueba unitaria.

    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;

Luego llama al Func en lugar del método real.

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

Para un ejemplo más completo, visite http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Rhyous
fuente
Esto es bueno, pero los lectores deben tener en cuenta que _DoWorkMethod es un nuevo campo de la clase que cada instancia de la clase ahora tiene que asignar un campo más. Es raro que esto importe, pero a veces lo hace dependiendo de la cantidad de instancias que esté asignando en cualquier momento. Puede solucionar este problema haciendo _DoWorkMethod estático. La desventaja es que si tiene pruebas unitarias ejecutándose simultáneamente, se completarán dos pruebas unitarias diferentes que pueden modificar el mismo valor estático.
zumalifeguard
-1

Entonces, si está utilizando Moq y quiere burlarse del resultado de un método de Extensión, puede usar SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) en la instancia de la clase simulada que tiene el método de extensión que está intentando burlarse.

No es perfecto, pero para fines de pruebas unitarias funciona bien.

ckernel
fuente
Creo que es SetReturnsDefault <T> ()
David
eso nunca devuelve la instancia concreta. ¡Simplemente nulo en el caso de una clase C # personalizada!
Hola Mundo