Mockups en MVC
En C#, el uso de mocks es común en pruebas unitarias, especialmente al probar métodos que dependen de servicios externos, bases de datos o cualquier otro componente que no se desee invocar directamente durante la prueba. Una de las bibliotecas más populares para mocking en .NET es Moq.
¿Qué es Mocking y por qué se usa?
Mocking es la práctica de crear objetos simulados que imitan el comportamiento de los objetos reales. Se usa en pruebas unitarias para:
- Aislar la unidad de código que se prueba.
- Evitar la ejecución de dependencias externas o lentas, como llamadas a la base de datos.
- Verificar interacciones, como si se llama a un método específico o si se pasan ciertos argumentos.
Instalación de Moq
Para usar Moq, primero debes instalar el paquete Moq. Si usas .NET CLI:
dotnet add package Moq
Ejemplos de uso de Mock en pruebas unitarias con Moq
Supongamos que tenemos una clase ServicioCliente
que depende de una interfaz IRepositorioCliente
para acceder a datos.
Código base
public interface IRepositorioCliente
{
Cliente ObtenerClientePorId(int id);
}
public class ServicioCliente
{
private readonly IRepositorioCliente _repositorioCliente;
public ServicioCliente(IRepositorioCliente repositorioCliente)
{
_repositorioCliente = repositorioCliente;
}
public string ObtenerNombreCliente(int id)
{
var cliente = _repositorioCliente.ObtenerClientePorId(id);
return cliente != null ? cliente.Nombre : "Cliente no encontrado";
}
}
public class Cliente
{
public int Id { get; set; }
public string Nombre { get; set; }
}
Prueba unitaria usando Mock con Moq
En esta prueba, queremos verificar que el método ObtenerNombreCliente
devuelve el nombre correcto sin llamar a una base de datos real.
using Moq;
using Xunit;
public class ServicioClienteTests
{
[Fact]
public void ObtenerNombreCliente_ClienteExiste_RetornaNombreCliente()
{
// Configurar el mock del repositorio
var mockRepositorio = new Mock<IRepositorioCliente>();
mockRepositorio.Setup(r => r.ObtenerClientePorId(1))
.Returns(new Cliente { Id = 1, Nombre = "Juan" });
// Inyectar el mock en el servicio
var servicio = new ServicioCliente(mockRepositorio.Object);
// Actuar
var nombreCliente = servicio.ObtenerNombreCliente(1);
// Afirmar
Assert.Equal("Juan", nombreCliente);
// Verificar que el método fue llamado con el parámetro específico
mockRepositorio.Verify(r => r.ObtenerClientePorId(1), Times.Once);
}
[Fact]
public void ObtenerNombreCliente_ClienteNoExiste_RetornaMensajeError()
{
var mockRepositorio = new Mock<IRepositorioCliente>();
mockRepositorio.Setup(r => r.ObtenerClientePorId(It.IsAny<int>()))
.Returns((Cliente)null);
var servicio = new ServicioCliente(mockRepositorio.Object);
var resultado = servicio.ObtenerNombreCliente(10);
Assert.Equal("Cliente no encontrado", resultado);
mockRepositorio.Verify(r => r.ObtenerClientePorId(10), Times.Once);
}
}
Explicación del ejemplo
- Crear el Mock: Se crea un mock de
IRepositorioCliente
usandovar mockRepositorio = new Mock<IRepositorioCliente>();
. - Configurar el Mock:
mockRepositorio.Setup(r => r.ObtenerClientePorId(1)).Returns(new Cliente { Id = 1, Nombre = "Juan" });
configura el métodoObtenerClientePorId
para que, cuando reciba el parámetro1
, devuelva un objetoCliente
conNombre = "Juan"
.- En la segunda prueba, configuramos
ObtenerClientePorId
para que devuelvanull
para cualquierid
.
- Inyectar el Mock: Pasamos
mockRepositorio.Object
al constructor deServicioCliente
. Esto permite que elServicioCliente
use el mock en lugar de una implementación real. - Verificar el Resultado:
- Usamos
Assert.Equal
para comprobar queObtenerNombreCliente
devuelve el nombre correcto o el mensaje de error según el caso. mockRepositorio.Verify
asegura queObtenerClientePorId
se haya llamado con el parámetro correcto y el número de veces especificado (Times.Once
en este caso).
- Usamos
Opciones Avanzadas con Moq
- Verificar llamadas específicas:
mockRepositorio.Verify(r => r.ObtenerClientePorId(It.IsAny<int>()), Times.Exactly(1));
- Excepciones simuladas:
mockRepositorio.Setup(r => r.ObtenerClientePorId(It.IsAny<int>())) .Throws(new Exception("Error en la base de datos"));
- Simulación de métodos
async
: Para métodos asíncronos, puedes usarReturnsAsync
.mockRepositorio.Setup(r => r.ObtenerClientePorIdAsync(It.IsAny<int>())) .ReturnsAsync(new Cliente { Id = 1, Nombre = "Juan" });
Con Moq
, puedes simular casi cualquier comportamiento y controlar todas las interacciones con dependencias, lo que hace que las pruebas unitarias sean más efectivas y fáciles de realizar en .NET.