c# - remarks - ¿Cómo uso Moq para simular un método de extensión?
remarks c# (6)
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. Mocking ese resultado parecía la elección obvia pero Moq no parece ofrecer una manera 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 pensar en esto de otra manera?
Aquí hay un ejemplo trivial que falla con la habitual "expectativa no válida en un miembro no invalidable" . Este es un mal ejemplo de la necesidad de burlarse de 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 aumenta en el corto plazo.
Creé una clase contenedora para los métodos de extensión que necesitaba para burlarme.
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 códigos con su contenedor IOC.
Los métodos de extensión son solo métodos estáticos disfrazados. Mocking frameworks como Moq o Rhinomocks solo pueden crear instancias simuladas de objetos, esto significa que no es posible burlarse de los métodos estáticos.
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.
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í hay algunos tutoriales también:
Si controla la definición de los métodos de extensión (es decir, no son los integrados de LINQ), hay otra alternativa, explicada en "Hacer que los métodos de extensión sean susceptibles de burlarse".
Si puede cambiar el código de los métodos de extensión, puede codificarlo de esta manera 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.
(Puede usar solo la clase de implementación sin métodos de extensión que son una especie de azúcar sintáctico).
Y puede burlarse de 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 myObject = new Object();
var myClassUsingExtensions = new MyClassUsingExtensions();
// When:
//-------
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));
}
}