Todos entendemos el valor de los test unitarios en mayor o menor medida, es decir, poder probar una parte de nuestro código sin intervención de las demás en un “ambiente” aislado y repetible, de modo de poder hacerlo muchas veces y progresivamente, etc. Bien, en ocasiones ocurre que nuestro código depende de componentes que podemos “aislar” porque, por ejemplo tenemos una interfaz, algo así:
public class Negocio { private IServicioExterno servicio; public Negocio(IServicioExterno servicio) { this.servicio = servicio; } public int HacerAlgo() { var valores = this.servicio.GetAll(); return valores++; } }
En este caso sería sencillo desentendernos de la dependencia IServicioExterno creando un mock, con Moq por ejemplo, y probar así:
[TestClass()] public class NegocioTests { [TestMethod()] public void HacerAlgoTest() { var mock = new Mock(); var target = new Negocio(mock.Object); mock.Setup((servicio) => servicio.GetAll()).Returns(1); var result = target.HacerAlgo(); Assert.AreEqual(2, result); } }
Lo que hacemos es crear un mock y configurarlo para que cuando se llame al servicio GetAll devuelva un 1, esto se puede hacer porque hay una interfaz de por medio, y además tenemos acceso al código de todos los componentes.
¿Qué pasa cuando no tenemos acceso a nuestras dependencias?
No es poco común que nuestro código dependa de un componente de terceros que no tiene una interfaz o que dependamos de un componente del mismo framework de .NET que no podemos controlar y que necesitamos aislar, el clásico ejemplo es el HttpContext en una aplicación web, pero podría ser cualquiera, ahí la cosa se complica.
En estos caso hay quien (y diría que todos lo hemos hecho) genera un wrapper y saca la interfaz afuera para poder cambiar sus dependencias de HttpContext a la interfaz y usar el wrapper contra el context real en la app y una mock en los tests.
Esto es bastante trabajo, y además estamos modificando nuestro código para poder probarlo, lo cual es, al menos, polémico; por otro lado no siempre vamos a poder “inyectar” esas dependencias, habrá casos en los que no podamos simplemente hacer un wrapper porque no tenemos ningún tipo de acceso.
Otro caso aún más polémico es definir mecanismos (inyecciones, interfaces) que realmente no aportan al real funcionamiento ni a la arquitectura solamente para poder, al momento de probar, poner un mock o aislarnos de nuestra base de datos.
Typemock al rescate
Typemock Isolator no es algo nuevo para nada más bien todo lo contrario, es un proyecto bien maduro que tiene varias funcionalidades que nos ayudan a hacer tests, como runners, code coverage, generador de tests automático, etc. En el caso del Isolator en sí, lo que hace es justamente eso: permitir interceptar las llamadas a cualquier objeto de la aplicación, si, cualquier objeto y hacer diferentes cosas:
- Reemplazarlo por un mock
- Interceptar y modificar el resultado
- Interceptar llamadas a métodos estáticos
- Interceptar llamas a constructores
- Correrlo contra nuestro entorno de CI
y algunas más, todo sorprendente, y aclaro que no es una herramienta gratuita pero creo que vale la pena, pero no demoremos más, vamos a ver cómo reemplazar una llamada a HttpContext con un mock nuestro
//decimos al Isolator que cualquier llamada a HttpContext.Current retorne un mock recursivamente Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake(); //ahora recuperamos el mock del objeto Session var fakeSession = HttpContext.Current.Session; //configuramos el comportamiento del Session para que retorne un 1 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1"); //verificamos el comportamiento Assert.AreEqual("1", fakeSession.SessionID);
Simple no? en este caso le decimos que hago mocks recursivos a partir de HttpContext.Current, después tomamos la sesión (fake) y le asignamos un valor, de ahí en adelante el código que use la sesión va a recibir lo que nosotros configuramos, sorprendente, no?
Conclusiones.
No son pocos los casos en los que agregamos una interaz solamente para poder inyectar un mock durante un test y esto es claramente trabajo de más y arquitectura de más, en este caso y en otros donde no es imposible controlar las dependencias (librería de terceros ya compiladas por ejemplo) Typemock es una excelente opción.
Nos leemos.