Mocking Static Methods

Recientemente, he comenzado a usar Moq para probar la unidad. Uso Moq para simular clases que no necesito probar.

¿Cómo manejas típicamente los métodos estáticos?

public void foo(string filePath) { File f = StaticClass.GetFile(filePath); } 

¿Cómo podría ser burlado este método estático, StaticClass.GetFile() ?

PD. Agradecería cualquier material de lectura que recomiendes en Moq y Unit Testing.

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.

También puedes buscar en Google para obtener más información.

Además, hay algunas preguntas formuladas anteriormente en StackOverflow aquí , aquí y aquí .

@ Pure.Krome: buena respuesta pero agregaré algunos detalles

@Kevin: debe elegir una solución según los cambios que pueda aportar al código.
Si puede cambiarlo, algunas inyecciones de dependencia hacen que el código sea más comprobable. Si no puedes, necesitas un buen aislamiento.
Con el framework de burlas gratuitas (Moq, RhinoMocks, NMock …) solo puedes simular delegates, interfaces y métodos virtuales. Entonces, para los métodos estáticos, sellados y no virtuales tienes 3 soluciones:

  • TypeMock Isolator (puede simular todo, pero es caro)
  • JustMock of Telerik (recién llegado, menos costoso pero aún no gratis)
  • Moles of Microsoft (la única solución gratuita para el aislamiento)

Recomiendo a Moles , porque es gratis, eficiente y usa expresiones lambda como Moq. Solo un detalle importante: los moles proporcionan talones, no burlas. Por lo tanto, puede seguir utilizando Moq para la interfaz y delegates;)

Mock: una clase que implementa una interfaz y permite la capacidad de establecer dinámicamente los valores a devolver / excepciones para arrojar desde métodos particulares y proporciona la capacidad de verificar si se han llamado / no llamado métodos particulares.
Stub: como una clase falsa, excepto que no proporciona la capacidad de verificar que los métodos hayan sido llamados / no llamados.

Hay posibilidad en .NET excluyendo MOQ y cualquier otra biblioteca burlona. Tienes que hacer clic derecho en el explorador de soluciones en el ensamblaje que contiene el método estático del que quieres simularte y elegir Agregar ensamblaje de falsificaciones . A continuación, puede burlarse libremente de los métodos estáticos de ensamblaje.

Suponga que desea System.DateTime.Now el método estático System.DateTime.Now . Haz esto, por ejemplo, de esta manera:

 using (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1); Assert.AreEqual(DateTime.Now.Year, 1837); } 

Tiene una propiedad similar para cada propiedad y método estático.

Puedes lograr esto con la biblioteca Pose disponible de nuget. Le permite simular, entre otras cosas, métodos estáticos. En su método de prueba, escriba esto:

 Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A())) .With((string name) => /*Here return your mocked value for test*/); var sut = new Service(); PoseContext.Isolate(() => result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim); 

Para leer más, consulte aquí https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8

Sé que esto es un poco tarde, pero esta solución indirecta me permitió burlarme de un método estático usando Moq.

Para hacer esto, creé una clase (llamémoslo Placeholder ) cuyo único método llamó al método estático StaticClass.GetFile .

 public class Placeholder{ //some empty constructor public File GetFile(){ File f = StaticClass.GetFile(filePath); return f; } } 

Entonces, en lugar de llamar a StaticClass.GetFile in foo , creé una instancia de Placeholder y llamé a la función GetFile .

 public void foo(string filePath) { Placeholder p = new Placeholder(); File f = p.GetFile(filePath); } 

Ahora, en pruebas unitarias, en lugar de intentar StaticClass.GetFile , pude GetFile método GetFile no estático de la clase Placeholder .

He estado jugando con un concepto de refactorización de los métodos estáticos para invocar a un delegado que puede establecer externamente para fines de prueba.

Esto no usaría ningún marco de prueba y sería una solución completamente personalizada, sin embargo, el refactor no influirá en la firma de la persona que llama, por lo que sería relativamente seguro.

Para que esto funcione, necesitaría tener acceso al método estático, por lo que no funcionaría para ninguna biblioteca externa, como System.DateTime .

Heres es un ejemplo con el que he estado jugando donde he creado un par de métodos estáticos, uno con un tipo de devolución que toma dos parámetros y un genérico que no tiene ningún tipo de devolución.

La clase estática principal:

 public static class LegacyStaticClass { // A static constructor sets up all the delegates so production keeps working as usual static LegacyStaticClass() { ResetDelegates(); } public static void ResetDelegates() { // All the logic that used to be in the body of the static method goes into the delegates instead. ThrowMeDelegate = input => throw input; SumDelegate = (a, b) => a + b; } public static Action ThrowMeDelegate; public static Func SumDelegate; public static void ThrowMe() where TException : Exception, new() => ThrowMeDelegate(new TException()); public static int Sum(int a, int b) => SumDelegate(a, b); } 

Las pruebas unitarias (xUnit y Shouldly)

 public class Class1Tests : IDisposable { [Fact] public void ThrowMe_NoMocking_Throws() { Should.Throw(() => LegacyStaticClass.ThrowMe()); } [Fact] public void ThrowMe_EmptyMocking_DoesNotThrow() { LegacyStaticClass.ThrowMeDelegate = input => { }; LegacyStaticClass.ThrowMe(); true.ShouldBeTrue(); } [Fact] public void Sum_NoMocking_AddsValues() { LegacyStaticClass.Sum(5, 6).ShouldBe(11); } [Fact] public void Sum_MockingReturnValue_ReturnsMockedValue() { LegacyStaticClass.SumDelegate = (a, b) => 6; LegacyStaticClass.Sum(5, 6).ShouldBe(6); } public void Dispose() { LegacyStaticClass.ResetDelegates(); } }