Muy buenas a todos. Después de un tiempo de inactividad bloguera, volvemos a la carga con uno de nuestros temas favoritos: el testeo unitario. Esta vez vamos a ver como podemos mockear (o hacer un doble de prueba, como prefiráis) un servicio WCF con la ayuda de Moq. Recordad que tenéis dos tutoriales introductorios a Moq en este mismo blog (parte 1, parte 2).
Para nuestro ejemplo vamos a utilizar uno de los servicios web que proporciona Sharepoint. Imaginaos que tenemos una clase que utiliza dicho servicio. Los pasos para hacerlo podrían ser:
- Agregar la referencia al servicio
- Crear una instancia de la clase cliente en nuestro código
private DwsSoapClient sharepointClient = new DwsSoapClient(); - Hacer la llamada a la función deseada
sharepointClient.CreateFolder(«http://share:81/sites/TestSite«);
Hasta aquí nada nuevo. ¿Pero qué pasa si queremos testear unitariamente nuestra clase? Pues para empezar lo que pasará es que si no hacemos ningún cambio, lo que nos salga no será un test unitario, pues necesitará de un elemento externo como puede ser un Sharepoint para poder funcionar. Lo que tendremos que hacer será mockear la clase proxy. Esto con Moq directamente no lo podemos hacer, pues esta clase no es abstracta ni tiene métodos virtuales. Si le echamos un ojo al fichero reference.cs que tiene la implementación del cliente (haciendo un Go to reference en una función) veremos que la clase implementa una interfaz del servicio. ¡Magnífico! Pues no, ya que los métodos que normalmente utilizaremos desde el cliente no se pueden acceder desde esta interfaz, ya que están directamente codificados en la clase. Lo mismo nos pasará con los métodos asíncronos si creamos el proxy del servicio con el check activado:
En este caso, Visual Studio pone estos métodos, y sus correspondientes eventos, directamente en el código de la clase cliente, y tampoco los añade a la interfaz. ¿Que opción tenemos? Pues crearnos nosotros una interfaz que incorpore los métodos asíncronos y los eventos para así ser capaz de mockearlos. También, apoyándonos en que la clase cliente es una clase parcial, haremos que la clase cliente implemente esta interfaz sin tener que tocar el fichero reference.cs, que es un fichero generado automáticamente por Visual Studio y no es nada recomendable modificarlo. Así pues, generaremos un fichero con el siguiente código, recordando de especificar el mismo namespace que la clase cliente.
public interface ISharepointDWSAsyncService : DwsSoap
{
void CreateFolderAsync(string url);
event System.EventHandler<CreateFolderCompletedEventArgs>
CreateFolderCompleted;
}
public partial class DwsSoapClient : ISharepointDWSAsyncService
{ }
En este caso sólo hemos añadido un método que será el que utilizaremos para el ejemplo, pero aquí tendríais que añadir todos los métodos y eventos que necesitéis.
Ahora que tenemos la interfaz, tenemos que modificar el código de nuestro SUT para que utilice la interfaz y no directamente la clase. Después utilizaremos inyección de dependencias para inyectar la implementación que nos interese. La inyección la podemos hacer de múltiples maneras, por ejemplo por constructor o por propiedad. Roy Osherove recomienda en su libro The Art of Unit Testing, with examples in .Net (que os recomiendo) que las dependencias obligatorias se inyecten por constructor y las opcionales por propiedad. Podéis utilizar esta regla como referencia. En nuestro caso, la inyectamos por propiedad:
private ISharepointDWSAsyncService sharepointClient;
public ISharepointDWSAsyncService SharepointClient
{
set
{
sharepointClient = value;
}
}
El resto del código del SUT debería quedar igual.
Ahora ya podemos pasar a escribir nuestro test. Gracias a las bondades de Moq, nos queda un código bastante compacto y elegante:
[TestMethod]
public void TestAsynCall()
{
Mock<IDAO> mockDao = new Mock<IDAO>();
Mock<ISharepointDWSAsyncService> sharepointMock = new
Mock<ISharepointDWSAsyncService>();
AssetManager assetManager =
new AssetManager(mockDao.Object);
assetManager.SharepointClient = sharepointMock.Object;
assetManager.CallWSAsync();
sharepointMock.Raise(sp => sp.CreateFolderCompleted += null,
new CreateFolderCompletedEventArgs(
new string[] { «CreateFolderCompleted» }, null, false, null));
Assert.AreEqual(«CreateFolderCompleted»,
assetManager.resultCall);
}
Como veis, primero creamos los dos mocks que utilizará nuestro SUT, uno lo inyectamos por constructor y otro por propiedad. Después hacemos la llamada a la función que hará la llamada asíncrona al servicio y después utilizamos la capacidad de Moq para lanzar eventos en un mock para lanzar el evento de completed de nuestra interfaz. Finalmente haremos el Assert que toque, en nuestro caso simplemente miraremos que se ha establecido el valor de una propiedad al valor correspondiente.
Y ya tenemos nuestro servicio WCF mockeado! Obviamente, esto último explicado sirve para cualquier clase asíncrona, no solo para servicios WCF. De echo, otra cosa que podemos hacer y que también os recomiendo es poner un nivel de indirección [1] entre vuestra clase y el servicio WCF, es decir, una clase intermedia con las llamadas y eventos que necesitéis. Esto os permitirá una mayor abstracción del servicio WCF (y poder cambiar su implementación sin sufrir demasiado) y el código seguramente os quedará más legible y usable.
Nos leemos!!
[1] –> «There is no object-oriented problem that cannot be solved by adding a layer of indirection, except, of course, too many layers of indirection.»