¡Hola!
En las dos anteriores entradas (aquí y aquí), hemos conocido un poco la herramienta Autofixture y cómo nos ayuda principalmente a aislarnos lo máximo posible en la etapa Arrange de los test y facilitar así nuestras refactorizaciones de código. Hoy vamos a ver otra herramienta similar, pero mucho más sencilla (y menos potente), AutoMocker. Está disponible directamente desde NuGet.
El objetivo de AutoMocker es simplemente desacoplarnos de los constructores en los test y, para ello, en todas las dependencias que se pasen por constructor va a generar automáticamente un Mock. AutoMocker lo utilizaremos principalmente en servicios que reciben dependencias como interfaces o clases abstractas, aunque realmente puede crear un Mock de todo lo que sea mockeable. De un string (es una clase sealed), no podrá crear un Mock, o de un int tampoco (es una estructura, no una clase).
Vamos a ver cómo lo podemos utilizar con un ejemplo. Nos imaginamos que tenemos un sistema que imprime el nombre de todos los empleados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public interface IPrinterEmployeesService { void Print(); } public class PrinterEmployeesService : IPrinterEmployeesService { private readonly IRepository<Employee> employeeRepository; public PrinterEmployeesService(IRepository<Employee> employeeRepository) { this.employeeRepository = employeeRepository; } public void Print() { var employees = employeeRepository.GetAll(); foreach (var employee in employees) { Console.WriteLine(employee.Name); } } } |
Muy sencillo, pasamos el repositorio de empleados como dependencia por constructor y en el método Print, recuperamos todos los empleados para pintarlos en pantalla. La interfaz IRepository y la clase Employee es la siguiente:
1 2 3 4 5 6 7 8 | public interface IRepository<T> { IEnumerable<T> GetAll(); } public class Employee { public string Name { get; set; } } |
Si queremos testear que se hace una llamada al método GetAll del repositorio de empleados, aunque seguramente no quieras testear eso :), lo haríamos de la siguiente forma:
1 2 3 4 5 6 7 8 9 10 | [Fact] public void PrinterEmployeesUseRepository_Test() { var repositoryMoq = new Mock<IRepository<Employee>>(); var printerEmployeesService = new PrinterEmployeesService(repositoryMoq.Object); printerEmployeesService.Print(); repositoryMoq.Verify(r => r.GetAll(), Times.AtLeastOnce); } |
Hemos creado un Mock del repositorio, lo pasamos por constructor a PrinterEmployeesService, hacemos la llamada al método Print y comprobamos que el método GetAll se ha llamado al menos una vez. El problema aquí es que estamos utilizando directamente el constructor de PrinterEmployeesService y cuando modifiquemos los parámetros que recibe, nos tocará cambiar todos los test que utilicen ese constructor. Este problema que estamos viendo es muy posible que no nos ocurra en el código de la aplicación, porque estamos utilizando algún contenedor de inversión de control como Ninject o Unity, donde registraremos la implementación de IRepository
1 2 3 4 5 6 7 8 9 10 11 | [Fact] public void PrinterEmployeesUseRepository_Test() { var mocker = new AutoMocker(); var printerEmployeesService = mocker.CreateInstance<PrinterEmployeesService>(); var repositoryMoq = mocker.GetMock<IRepository<Employee>>(); printerEmployeesService.Print(); repositoryMoq.Verify(r => r.GetAll(), Times.AtLeastOnce); } |
¿Qué estamos haciendo? Creamos una instancia de AutoMocker y le solicitamos que nos cree una instancia de nuestro sut, en este caso PrinterEmployeesService. Él se va a encargar de instanciar PrinterEmployeesService con los parámetros que requiera, en este caso generará un Mock de IRepository<Employee>. Posteriormente obtenemos obtenemos el mock que ha creado de IRepository<Employee> con el método GetMock, y hacemos las comprobaciones del test.
¿Qué pasa si añadiésemos un nuevo parámetro al constructor de PrinterEmployeesService?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class PrinterEmployeesService : IPrinterEmployeesService { private readonly IRepository<Employee> employeeRepository; private readonly IPrinterService printerService; public PrinterEmployeesService(IRepository<Employee> employeeRepository, IPrinterService printerService) { this.employeeRepository = employeeRepository; this.printerService = printerService; } public void Print() { var employees = employeeRepository.GetAll(); foreach (var employee in employees) { printerService.Print(employee.Name); } } } |
Hemos sustituido la sentencia Console.WriteLine(employee.Name) por printerService.Print(employee.Name), donde printerService es una dependencia que hemos pasado por constructor. Si volvemos a ejecutar el test que vimos un poco más arriba, sigue poniéndose en verde ya que AutoMocker se ha encargado de resolver el constructor mockeando la nueva dependencia. Como vemos, tras cambiar nuestro base de código no nos hemos visto obligados a volver a tocar test que ya habíamos creado.
Si se quiere configurar la creación de un tipo en concreto, se puede hacer con la sentencia Use:
1 2 3 4 5 6 7 8 9 10 11 12 | [Fact] public void PrinterEmployeesUseRepository_Test() { var mocker = new AutoMocker(); Mock<IRepository<Employee>> repositoryMoq = new Mock<IRepository<Employee>>(); mocker.Use<IRepository<Employee>>(repositoryMoq); var printerEmployeesService = mocker.CreateInstance<PrinterEmployeesService>(); printerEmployeesService.Print(); repositoryMoq.Verify(r => r.GetAll(), Times.AtLeastOnce); } |
Como vemos en el test anterior, hemos configurado AutoMocker para que utilice el mock de IRepository<Employee> que previamente hemos creado. Esto puede ser útil si por ejemplo queremos que uno de nuestros mocks, típicamente sobre el que comprobaremos algo, lo queremos en modo estricto (MockBehavior.Strict). En este ejemplo he configurado IRepository<Employee> para que utilice un mock, pero podría ser una implementación normal (por ejemplo, new FakeRepositoryEmployee()).
Automocker también permite combinar varios tipos y crear un mock que contenga todos los métodos de los tipos combinados:
1 2 3 4 5 6 7 8 9 10 | [Fact] public void PrinterEmployeesCombine_Test() { var mocker = new AutoMocker(); mocker.Combine<IRepository<Employee>,IPrinterService>(); var printerEmployeesService = mocker.CreateInstance<PrinterEmployeesService>(); printerEmployeesService.Print(); Assert.Same(mocker.Get<IRepository<Employee>>(),mocker.Get<IPrinterService>()); } |
Hemos combinado dos interfaces y comprobamos que, cuando recuperamos la instancia de cada una de ellas, es la misma, es decir, ha creado un mock que contiene todos los métodos de ambas interfaces y siempre devuelve la misma instancia. Por lo visto, esto se llama Forwarding y algunos contenedores de inversión de control lo tienen (como Castle Windsor). Personalmente para los test no he llegado a encontrar un uso claro.
Automocker también incluye algún acceso directo para comprobar ciertas cosas de todos los mocks que se hayan generado:
1 2 3 4 5 6 7 8 9 10 | [Fact] public void PrinterEmployeesUseRepository_Test() { var mocker = new AutoMocker(); var printerEmployeesService = mocker.CreateInstance<PrinterEmployeesService>(); printerEmployeesService.Print(); mocker.Verify<IRepository<Employee>>(r => r.GetAll(), Times.AtLeastOnce); } |
Vemos que nos proporciona un acceso directo y ya no necesitamos obtener el Mock del repositorio para hacer la comprobación. Además de Verify, también expone VerifyAll, Setup, SetupAllProperties, etc, lo que reduce el código que necesitamos en el test.
Conclusiones
Después de haber visto en las anteriores entradas Autofixture, podemos ver que está mucho más limitado y parece orientado principalmente a desacoplar los constructores principalmente en servicios que reciben servicios como dependencias. De todas maneras es bastante más sencillo de utilizar y puede que para proyectos que no sean muy grandes puede venir bien, aunque a lo mejor en estos proyectos (no demasiado grandes) quizá añada complejidad y no sea tan crítico tener que refactorizar tests al cambiar constructores de servicios.
¡Un saludo!