Pruebas Unitarias en .Net Core con xUnit – 2 – Inyección de Dependencia

Pruebas Unitarias en .Net Core con xUnit – 2 – Inyección de Dependencia

En este Post nos enfocamos en una de las primeras técnicas que necesitas poder aplicar Pruebas Unitarias de una manera efectiva. Si leíste el post de la semana pasada, y estuviste practicando hacer pruebas unitarias con tu código, rápidamente has de haber encontrado un problema muy común que complica hacer una prueba, el caso en el que una clase depende de otra.

Si apenas estas empezando con pruebas unitarias, y no has leído los 2 posts anteriores, te recomiendo que vayas y empieces por ellos:

El Problema: Pruebas con Dependencias

Un ejemplo común de este tipo de dependencia es usar clases responsables de extraer o insertar información en algún servicio externo, como una Base de Datos, o una llamada a un API REST, o un Servicio de Correos. Si tienes una función que utiliza una de esas clases, te darás cuenta de que necesitas inicializar la clase que realiza la comunicación para poder probar tu función. Esto rompe la intención de una prueba unitaria. A esto se le llama una dependencia, ya que, para ejecutar tu función o clase, dependes de otra clase, lo cual no suele ser bueno.

Como ejemplo, la siguiente clase solicita el listado de todos los clientes, y regresa un listado con un mensaje de saludo para cada uno.

//MensajesClientes.cs
public class MensajesClientes
{
    public List<string> CrearSaludosClientes()
    {
        var clienteRepository = new ClientesRepository();
        var clientes = clienteRepository.GetAll();
        var resuesta = new List<string>();
        foreach (var cliente in clientes)
        {
            var mensaje = "Hola " + cliente.Nombre + " " + cliente.Apellido;
            resuesta.Add(mensaje);
        }
        return resuesta;
    }
}

El problema aquí es. que cuando tratamos de hacer una prueba unitaria; ¿Como podemos validar el comportamiento de la función que conecta a una Base de Datos real?

Recuerda, una prueba unitaria nunca debe de probar la comunicación con sistemas externos, únicamente debe probar la unidad de código. Sin embargo, ClientesRepository requiere de una Base de Datos para poder funcionar, por lo que MensajesClientes también requiere de una Base de Datos para funcionar. Esto se le suele llamar Acoplamiento Fuerte (Strong Coupling) entre las clases.

En resumen, no hay manera de que hacer una prueba unitaria del método CrearSaludosClientes, sin hacer una conexión a la base de datos.

Inyección de Dependencia (Dependency Injection)

La manera más común para solucionar este tipo de dependencias es usando el patrón de Inyección de Dependencia (Dependency Injection). Este es un patrón que se considera como una implementación de una idea un más global, la Inversión de Control.

También es una de las maneras más utilizadas para cumplir con el “Open-Closed Principle” el cual estipula que las entidades del software (clases, módulos, funciones, etc.) deben estar abiertas a la extensión, pero cerradas para modificaciones. Su correcta aplicación busca reducir la dependencia entre diferentes clases.

“Open-Closed Principle – Las entidades de código deben estar abiertas para extensión, pero cerradas para modificaciones”.

En palabras puede sonar un poco abrumador, pero no te preocupes. Como todo en el software, la implementación es mucho mas simple que todas las palabras que necesitamos para describirlas.

Para implementar la Inyección de Dependencia, lo primero que hacemos es extraer todas las dependencias, para que estas sean inyectadas en vez de inicializadas dentro de la misma clase. Esto lo hacemos requiriendo la clase ya inicializada en el constructor:

//MensajesClientes.cs
public class MensajesClientes
{
    ClientesRepository _clientesRepository;

    public MensajesClientes(ClientesRepository clienteRepository) 
    {
        _clientesRepository = clienteRepository;
    }

    public List<string> CrearSaludosClientes()
    {
        var clientes = _clientesRepository.GetAll();
        var resuesta = new List<string>();
        foreach (var cliente in clientes) 
        {
            var mensaje = "Hola " + cliente.Nombre + " " + cliente.Apellido;
            resuesta.Add(mensaje);
        }
        return resuesta;
    }
}

En esta implementación, el control de lo que va a hacer ClienteRepository ya no depende de MensajesClientes, ya que el solo la recibe. Por eso se llama Inversión de Control, porque en lugar de ser el código de la implementación (MensajesClientes) sea quien controla la implementación y ejecución de ClientesReporitory, esa responsabilidad pasa a quien está iniciando MensajesClientes, reduciendo la dependencia entre ambas clases.

Y Ok. La realidad es que este cambio no ayuda mucho. ClienteRepository sigue siendo la misma clase, por lo que su ejecución será exactamente lo mismo, y aquí es donde pasamos al siguiente paso, como romper la dependencia.

Romper la Dependencia

Para entender como rompemos la dependencia, primero hay que tener muy clara la diferencia entre lo que la MensajesCliente necesita, y lo que ofrece la implementación ClientesRepository.

MensajesClientes no requiere a ClienteRepository como tal. Lo que requiere realmente es una función que le regrese el listado de los clientes (ver anotaciones). Es decir, cualquier clase que ejecutando la función GetAll() le regrese el listado que necesita, seria suficiente para cubrir su requerimiento. A esto lo llamamos una Abstracción. ClienteRepository es únicamente una clase que cumple dicho requerimiento.

¿Así que como rompemos la dependencia? Implementando que requerimos una clase que nos ofrezca la función GetAll(). Esto lo hacemos implementando la inyección de una Interfaz que cumpla dicha función. Y posteriormente haciendo que ClienteRepository implemente dicha Interfaz.

Si no estás familiarizado con el concepto y uso de Interfaces, Una Interfaz es es una plantilla que define las propiedades y métodos con las que tiene que cumplir una clase. De una manera es un Contrato, o Acuerdo, entre el requerimiento de una clase, y la implementación de otra.

Una Interfaz es simplemente un contrato entre el requerimiento de una clase, y la implementación de otra.

Este contrato nos permite “Romper” la dependencia explicita entre MensajesClientes y ClientesRepository. A esto se le suele llamar un Acoplamiento Débil (Loose Coupling) entre las clases. Se considera débil porque una clase ya no depende de otra, sino que depende de una Interfaz, la cual puede implementar otra clase y hacer que MensajesClientes siga funcionando.

En este caso lo hacemos de la siguiente manera:

1 – La Interfaz

Lo primero que hacemos es implementar dicha Interfaz. Las Interfaces se suelen nombrar poniendo una I mayúscula como primera letra del nombre. Esto es estándar en casi cualquier lenguaje así que es importante respetarlo.

//IClientesRepository.cs
public interface IClientesRepository
{
    public List<Cliente> GetAll();
}

2 – Que ClientRepository implemente la Interfaz

Lo segundo, hacemos que ClienteRepository implemente dicha Interfaz. Recuerda que esta clase es solo un ejemplo, esta clase realmente se encarga de hacer la conexión con la Base de Datos y extraer los clientes.

//ClientesRepository.cs
public class ClientesRepository : IClientesRepository
{
    public List<Cliente> GetAll()
    {
        //Aqui van los procesos para extraer de Base de Datos
        return new List<Cliente>();
    }
}

3 – Que MensajesClientes reciba la Interfaz para la Inyección

Tercero y último, actualizamos la clase MensajesClientes para recibir una instancia que respete la Interfaz IClientesRepository:

//MensajesClientes.cs
public class MensajesClientes
{
    IClientesRepository _clientesRepository; //Interfaz en vez de clase.

    public MensajesClientes(IClientesRepository clienteRepository) //Interfaz en vez de clase.
    {
        _clientesRepository = clienteRepository;
    }

    public List<string> CrearSaludosClientes()
    {
        var clientes = _clientesRepository.GetAll();
        var resuesta = new List<string>();
        foreach (var cliente in clientes) 
        {
            var mensaje = "Hola " + cliente.Nombre + " " + cliente.Apellido;
            resuesta.Add(mensaje);
        }
        return resuesta;
    }
}

Y de esa manera rompimos la dependencia de ClientesRepository. Al depender ahora de una Interfaz, podemos crear nuevas clases que implementen la misma Interfaz, pero que funcionen diferente.

  • Ej: En vez de que GetAll() te regrese un listado de todos los clientes, podría regresarte únicamente un listado de los clientes del usuario actual.

Puedes pensar que no es mucha ayuda, o que es simplemente es más código sin mucho beneficio. Pero como cualquier programador Sr te lo podrá confirmar, este simple cambio te abre las puertas a un código mucho más desacoplado, más fácil de extender, más fácil de mantener y, lo mas importante para nuestro caso, más fácil de probar.

¿Como nos ayuda esto para la prueba Unitaria? – Mocks

Ahora si podemos crear una prueba unitaria para este código, simplemente creando una clase adicional que implemente la interfaz IClientesRepository, con su función GetAll(), pero sin las llamadas a una base de datos real. Esto es lo que llamamos Mocks. Objetos que simulan comportamientos más complejos, de una manera simplificada, especificamente para poder probar.

Un Mock es un objeto que simula el comportamiento de otro, de una manera simplificada.

Recuerda que los Mocks se agregan en el proyecto de pruebas, ya que únicamente se usarán para pruebas. El estándar para nombrar las clases Mock es usar el mismo nombre del objeto que sustituyen, con la palabra Mock al final. En este caso vamos a crear un Mock de ClientesRepository por lo que nuestra clase Mock se llamara ClientesRepositoryMock, e implementa la interfaz IClientesRepository.

//ClientesRepositoryMock.cs
public class ClientesRepositoryMock : IClientesRepository
{
    public List<Cliente> GetAll()
    {
        var clientes = new List<Cliente>()
        {
            new Cliente()
            {
                IdCliente = Guid.NewGuid(),
                Nombre = "Pedro",
                Apellido = "Paramo",
            },
            new Cliente()
            {
                IdCliente = Guid.NewGuid(),
                Nombre = "Juan",
                Apellido = "Rulfo",
            },
        };
        return clientes;
    }
}

Como puede ver en esta clase, en vez de ir y tomar los clientes de una Base de Datos real, simplemente creamos unos objetos en memoria, de manera que, cuando se llame la función GetAll(), te regrese el listado de dichos objetos, simulando la conexión y arrojando lo que CrearSaludosClientes() requiere para probar.

De esta manera nuestra prueba unitaria quedaría así:

//MensajesClientes_Should.cs
public class MensajesClientes_Should
{
    [Fact]
    public void ValidarCrearSaluodosClientes() 
    {
        //Arrange
        var repoMock = new ClientesRepositoryMock();
        var sut = new MensajesClientes(repoMock);

        //Act
        var listaMensajes = sut.CrearSaludosClientes();

        //Assert
        Assert.NotEmpty(listaMensajes);
    }
}

Y con esto tenemos una Prueba Unitaria de una Clase o Método que tiene dependencia de otra clase.

Para explicar un poco lo que está pasando en la prueba. Básicamente se inicializa un objeto ClientesRepositoryMock y este se inyecta a la clase MensajesClientes, que es la que queremos probar, durante su creación. De esta manera, cuando se llama la función CrearSaludosCliente(), en vez de utilizar el objeto ClientesRepository, se utiliza el Mock, el cual simula la respuesta de la base de datos al llamar la función GetAll(), regresando un listado de clientes.

El Assert que incluimos es muy sencillo, únicamente revisa que la variable listaMensajes no esté vacía, pero en este caso sería necesario implementar Asserts adicionales, específicamente verificar que los mensajes se hayan creado de manera correcta. Intenta implementarlos como ejercicio y práctica.

Estos ejemplos son implementaciones completamente manuales de la Inyección de Dependencia y de cómo hacer un Mock. Existen librerías que te permiten hacer esto de manera más ágil. Pero con lo que aprendiste aquí, deberías de poder implementar Pruebas Unitarias a la mayoría de tus clases.

Recuerda que también es importante aprender las prácticas de Código Limpio “Clean Code” ya que, si te empiezas a encontrar con clases que tienen 2, 3 o más dependencias, o que tienen más de 20 líneas y hacen más de 1 cosa, tal vez lo que debas de volver a pensar es en tu diseño para simplificarlo y hacerlo más limpio, y unitario. Te dejo aquí la liga para el libro Clean Code de Uncle Bob por si quieres adquirirlo. Recuerda que si compras por medio de la liga estas apoyando el blog:

Resumen:

El primer obstáculo con el que todo programador se enfrenta al comenzar a hacer pruebas unitarias es como probar clases que dependen de otras clases. Esto lo resolvemos implementando el patrón de Inyección de Dependencia, donde extraemos la dependencia a una Interfaz que podamos usar para simular (Mock) dicho objeto.

Y el acordeón de esta Post es:

  • Si tu clase tiene una dependencia de otra clase, va a ser imposible probarla sin desacoplar primero dicha dependencia. Esto te lo encontrarás principalmente en clases que hacen llamadas a Bases de Datos, APIs, o cualquier servicio externo.
  • Para desacoplarlas, utilizamos el patrón de Inyección de Dependencia, que establece inyectar la Interfaz que cumpla con los requerimientos de tu clase, en vez de una clase completa.
  • La Interfaz es simplemente un contrato entre el requerimiento de una clase, y la implementación de otra.
  • Los pasos para implementar inyección de dependencia son:
    • Creamos una Interfaz con los métodos que necesita nuestra clase.
    • Hacemos que la clase de la que depende, implemente la interfaz.
    • Durante la construcción de nuestra clase, solicitamos la inyección de un objeto que cumpla con la Interfaz.
  • Con una Interfaz creada, podemos crear Mocks en nuestro proyecto de pruebas, para simular las funciones de la clase de la que depende nuestro método.

Anotaciones:

  1. Lo sé, la manera más fácil de solucionar esto sería agregar el listado como un parámetro de entrada en la función y extraer la responsabilidad de la creación de la lista a otro lado. Pero este es un ejemplo para que entiendas Inyección de Dependencia el cual va a ser muy importante para el resto de tus pruebas unitarias. Principalmente para entender como puedes implementar pruebas unitarias en código legado, y como implementarlas en controladores.

This Post Has 3 Comments

Deja un comentario

Close Menu