verifiable setup net mock dotnet c# unit-testing nunit mocking

setup - Prueba unitaria HttpContext.Current.Cache u otros métodos del lado del servidor en C#?



moq verifiable (14)

El consenso general parece ser que impulsar cualquier cosa relacionada con HttpContext desde una prueba unitaria es una pesadilla total, y debe evitarse si es posible.

Creo que estás en el camino correcto con respecto a la burla. Me gusta RhinoMocks ( http://ayende.com/projects/rhino-mocks.aspx ).

También he leído algunas cosas buenas sobre MoQ ( http://code.google.com/p/moq ), aunque todavía no lo he probado.

Si realmente desea escribir UI web comprobables de la unidad en C #, la forma en que las personas parecen estar dirigidas es utilizar el marco MVC ( http://www.asp.net/mvc ) en lugar de WebForms ...

Al crear una prueba unitaria para una clase que utiliza la clase HttpContext.Current.Cache , HttpContext.Current.Cache un error al usar NUnit. La funcionalidad es básica: compruebe si un elemento está en la caché, y si no, créelo y póngalo:

if (HttpContext.Current.Cache["Some_Key"] == null) { myObject = new Object(); HttpContext.Current.Cache.Insert("Some_Key", myObject); } else { myObject = HttpContext.Current.Cache.Get("Some_Key"); }

Al invocar esto desde una prueba unitaria, falla en NullReferenceException al encontrar la primera línea de Cache . En Java, usaría Cactus para probar el código del servidor. ¿Hay alguna herramienta similar que pueda usar para el código C #? Esta pregunta SO menciona marcos falsos: ¿es esta la única forma en que puedo probar estos métodos? ¿Hay alguna herramienta similar para ejecutar pruebas de C #?

Además, no compruebo si el Cache es nulo, ya que no quiero escribir código específicamente para la prueba unitaria y supongo que siempre será válido cuando se ejecute en un servidor. ¿Es esto válido, o debería agregar controles nulos alrededor de la caché?


La forma de hacerlo es evitar el uso directo de HttpContext u otras clases similares, y sustituirlas por simulaciones. Después de todo, no está tratando de probar que el HttpContext funciona correctamente (ese es el trabajo de Microsoft), solo está tratando de probar que los métodos se llamaron cuando deberían.

Pasos (en caso de que solo quiera conocer la técnica sin excavar a través de montones de blogs):

  1. Cree una interfaz que describa los métodos que desea usar en el almacenamiento en caché (probablemente cosas como GetItem, SetItem, ExpireItem). Llámalo ICache o lo que quieras

  2. Cree una clase que implemente esa interfaz y pase los métodos al HttpContext real

  3. Cree una clase que implemente la misma interfaz, y simplemente actúe como una memoria caché simulada. Puede usar un diccionario o algo si te importa guardar objetos

  4. Cambie su código original para que no use el HttpContext, y en su lugar solo use un ICache. El código tendrá que obtener una instancia del ICache: puede pasar una instancia en el constructor de las clases (esto es todo lo que realmente es la inyección de dependencia) o pegarla en alguna variable global.

  5. En su aplicación de producción, configure ICache para que sea su HttpContext-Backed-Cache real, y en las pruebas de su unidad, configure ICache para que sea el caché falso.

  6. ¡Lucro!


Si usa .NET 3.5, puede usar System.Web.Abstractions en su aplicación.

Justin Etheredge tiene una excelente publicación sobre cómo burlarse de HttpContext (que contiene la clase de caché).

Del ejemplo de Justin, paso una HttpContextBase a mis controladores usando HttpContextFactory.GetHttpContext. Al burlarse de ellos, simplemente construyo un simulacro para hacer llamadas al objeto de caché.


Todas estas preguntas de programación requieren un modelo de programación basado en interfaz, en el que implemente la interfaz dos veces. Uno para el código real y otro para la maqueta.

La instanciación es entonces el próximo problema. Hay varios patrones de diseño que se pueden usar para eso. Ver, por ejemplo, los famosos GangOfFour Creational patterns ( GOF ) o los patrones de Dependency Injection.

ASP.Net MVC está utilizando este enfoque basado en la interfaz y, por lo tanto, es mucho más adecuado para las pruebas unitarias.


Estoy de acuerdo con los demás en que usar una interfaz sería la mejor opción, pero a veces simplemente no es factible cambiar un sistema existente. Aquí hay un código que simplemente mezclé de uno de mis proyectos que debería darte los resultados que estás buscando. Es lo más alejado de una solución bonita o excelente, pero si realmente no puede cambiar su código, entonces debería hacer el trabajo.

using System; using System.IO; using System.Reflection; using System.Text; using System.Threading; using System.Web; using NUnit.Framework; using NUnit.Framework.SyntaxHelpers; [TestFixture] public class HttpContextCreation { [Test] public void TestCache() { var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null); var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { }); SetPrivateInstanceFieldValue(result, "m_HostContext", context); Assert.That(HttpContext.Current.Cache["val"], Is.Null); HttpContext.Current.Cache["val"] = "testValue"; Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue")); } private static HttpContext CreateHttpContext(string fileName, string url, string queryString) { var sb = new StringBuilder(); var sw = new StringWriter(sb); var hres = new HttpResponse(sw); var hreq = new HttpRequest(fileName, url, queryString); var httpc = new HttpContext(hreq, hres); return httpc; } private static object RunInstanceMethod(object source, string method, object[] objParams) { var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; var type = source.GetType(); var m = type.GetMethod(method, flags); if (m == null) { throw new ArgumentException(string.Format("There is no method ''{0}'' for type ''{1}''.", method, type)); } var objRet = m.Invoke(source, objParams); return objRet; } public static void SetPrivateInstanceFieldValue(object source, string memberName, object value) { var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); if (field == null) { throw new ArgumentException(string.Format("Could not find the private instance field ''{0}''", memberName)); } field.SetValue(source, value); } }



Como todos dijeron aquí, hay un problema con HTTPContext, actualmente Typemock es el único framework que puede simularlo directamente sin envoltorios o abstracciones.


El objeto de caché es difícil de simular porque es un área sellada del framework .NET. Normalmente soluciono esto construyendo una clase de contenedor de caché que acepta un objeto de administrador de caché. Para las pruebas, utilizo un administrador de caché falso; para producción utilizo un administrador de caché que realmente accede a HttpRuntime.Cache.

Básicamente, abstrae el caché yo mismo.


HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));


Esto quizás en tu calle ... Phil Haack luce, con la ayuda de Rhino, cómo se burla del httpcontext en asp mvc, pero imagino que se puede aplicar a webforms.

Clicky !!

Espero que esto ayude.


Un ejemplo para aquellos que usan MVC 3 y MOQ:

Mi método de controlador tiene la siguiente línea:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] as Dictionary<int, string>);

Como tal, cualquier prueba de unidad fallará ya que no estoy configurando HttpContext.Cache.

En mi prueba de unidad, organizo lo siguiente:

HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>(); var mockRequest = new Mock<HttpRequestBase>(); mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost")); var context = new Mock<HttpContextBase>(MockBehavior.Strict); context.SetupGet(x => x.Request).Returns(mockRequest.Object); context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache); var controllerContext = new Mock<ControllerContext>(); controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object); customerController.ControllerContext = controllerContext.Object;


Hay un enfoque más nuevo para ayudar a lidiar específicamente con Cache en pruebas unitarias.

Recomendaría utilizar el nuevo enfoque MemoryCache.Default de Microsoft. Tendrá que utilizar .NET Framework 4.0 o posterior e incluir una referencia a System.Runtime.Caching.

Vea el artículo aquí -> http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default funciona tanto para aplicaciones web como no web. Entonces, la idea es actualizar su aplicación web para eliminar las referencias a HttpContext.Current.Cache y reemplazarlas por referencias a MemoryCache.Default. Más tarde, cuando corras decide probar la Unidad estos mismos métodos, el objeto de caché todavía está disponible y no será nulo. (Porque no depende de un HttpContext).

De esta forma, ni siquiera es necesario que se burle del componente de caché.


Si no te importa probar el caché, puedes hacerlo a continuación:

[TestInitialize] public void TestInit() { HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null)); }

También puedes moq como a continuación

var controllerContext = new Mock<ControllerContext>(); controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser); controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));


Podría intentar...

Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake(); var fakeSession = HttpContext.Current.Session; Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");