MockObjects con Rhino.Mocks

Cuando escribimos tests de unidad, intentamos testear una única funcionalidad. Esto muchas veces es bastante difícil, ya que generalmente necesitamos apoyarnos en otras clases distintas para cumplir con el propósito de la testeada, y terminamos probando, al final, otras cosas. Y si fallan estas clases “base”, a veces es difícil rastrear cuál es el error.

Por ejemplo, supongamos que tenga un método RegistrarAsistencia(int, int), que registra que la persona asistió al evento (los argumentos serían los IDs). Este método inventado, tendría como dependencia la clase ControladorPersonas y ControladorEventos, para levantar los objetos necesarios y realizar el registro. Pero esto (levantar los objetos Persona y Registro), a nosotros, no nos interesa probar en el test de unidad, sino verificar que el método RegistrarAsistencia realmente funciona, registrando que la persona asistió al evento.

Otro ejemplo podría ser algún método ImprimirFactura, que toma los datos del formulario, e imprime la factura. ¿Cómo podemos escribir un test de unidad para esto?

Una solución viene de la mano de la técnica de MockObjects, y Rhino.Mocks es un framework para .NET.


Para el caso del método RegistrarAsistencia, podríamos crear objetos mock que simulen a los respectivos controladores, e indicarles a los mismos que cuando sean llamados con determinados argumentos, retornen determinados objetos. De esta forma, la lógica del método a probar siempre obtendrá resultados de los controladores, no hay que preparar la base de datos, y obtenemos un comportamiento determinístico.

Necesitamos manejarnos con interfaces. Así, para el controlador de personas, habrá una interfaz IControladorPersonas que éste implementará. Así podemos escribir un test de unidad utilizando Rhino.Mocks de la siguiente forma:

[Test]
public void RegistrarAsistencia()
{
	IControladorPersonas cp = this.mocks.CreateMock();
	Expect.Call(cp.ObtenerPersona(38))
		.Return(new Persona("Pividori", "Milton"));
	ControladorPersonas.Instancia = cp;

	IControladorEventos ce = this.mocks.CreateMock();
	Expect.Call(ce.ObtenerEvento(11))
		.Return(new Evento(new DateTime(1, 1, 2000)));
	ControladorEventos.Instancia = ce;

	ISession session = this.mocks.CreateMock();
		Expect.Call(session.Save(1))
			.Return(null)
			.IgnoreArguments();
		Expect.Call(session.Close());

	this.mocks.ReplayAll();

	Asistencia asistencia = controladorAsistencias.RegistrarAsistencia(session, 38, 11);
	Assert.AreEqual("Pividori", asistencia.Persona.Apellido);
	Assert.AreEqual(new DateTime(1, 1, 2000), asistencia.Event.Fecha);

	this.mocks.VerifyAll();
}

El objeto this.mocks es de tipo MockRepository, y lo utilizamos para crear nuestros objetos mock. En la primera línea estamos creando un objeto simulado (mock) que implementará la interfaz IControladorPersonas. Luego lo configuramos para que al ejecutarse en el mismo el método ObtenerPersona con el argumento “38”, éste devuelva el objeto Persona que creamos allí. Como RegistrarAsistencia utilizará la propiedad estática Instancia para obtener el controlador que necesita, guardamos en éste el objeto mock que acabamos de crear.

Al crear el objeto simulado con el método CreateMock, estamos diciéndole a Rhino.Mocks que verifique estrictamente la llamada a los métodos que definimos. En este caso no tiene mucho sentido porque se llama un sólo método, pero al ser varios, el test fallará si los mismos no se llaman en orden, o incluso si se llama otro que no hayamos definido. Hay otras opciones, como DynamicMock que exige que se llamen todos los métodos definidos, pero no en orden estricto, y si se llama a uno no definido retornará 0 o null, según corresponda.

Luego de configurar el objeto IControladorEventos, creamos un objeto de tipo ISession (de NHibernate), que pasamos al método RegistrarAsistencia para que utilice. Grabamos los métodos “Save” y “Close”, que deberán llamarse en ese orden.

Al terminar la fase de configuración, pasamos a la ejecución llamando this.mocks.ReplayAll(). Aquí ejecutamos el método por probar. Luego verificamos los resultados con NUnit. Finalmente llamamos a VerifyAll para verificar que la ejecución se halla llevado a cabo según nuestra configuración.

Para el otro caso, ImprimirFactura, podemos utilizar algo similar. Supongamos que tenemos una clase que se encarga de las funciones de la impresora. Podríamos crear una interfaz, configurar los métodos necesarios, y correr los test todas las veces que quieramos para verificar que el método funciona, aislando en el test de unidad sólo la porción de código que nos interesa probar.