Librerías de mocking para C# .NET

Mario S. Pinto Miranda

Publicado: 2023-11-16


La importancia de aprender a usar mocks durante el desarrollo de software radica esencialmente en dos direcciones: velocidad del feedback y validar las interacciones entre los objetos para lograr objetivos específicos. De esto escribí este post blog con más detalles sobre por qué los mocks son una herramienta de diseño de software cuando estamos desarrollando con el paradigma de orientación a objetos. En este post veremos cómo trabajar con mocks en C# .NET.

Disclaimer: No voy a seguir convenciones de .NET

Librerías

Hay varias librerías disponibles. Vamos a ver cada una brevemente y también para hacer una comparativa práctica vamos a ponernos en la situación de que queremos registrar un usuario en nuestra aplicación y esto implica guardar su información en repositorio. En definitiva, vamos a desarrollar este caso de uso, para ellos tenemos la siguiente interfaz:

public interface UserRepository
{
    void SaveUser(User user);
}

Moq

Características:

Limitaciones:

En general, Moq es una herramienta poderosa y ampliamente utilizada para crear mocks en .NET, pero es importante tener en cuenta sus limitaciones y usarla adecuadamente según el contexto de tu proyecto y las necesidades de tus pruebas unitarias.

Ejemplo sobre el caso de uso:

using Moq;
using Xunit;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var userRepositoryMock = new Mock<UserRepository>();
        var registerUser = new RegisterUser(userRepositoryMock.Object);
        var userToRegister = new User { /* User data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        userRepositoryMock.Verify(repo => repo.SaveUser(It.IsAny<User>()), Times.Once);
    }
}

NSubstitute

Características:

Limitaciones:

Ejemplo sobre el caso de uso:

using NSubstitute;
using Xunit;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var userRepositoryMock = Substitute.For<UserRepository>();
        var registerUser = new RegisterUser(userRepositoryMock);
        var userToRegister = new User { /* User data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        userRepositoryMock.Received(1).SaveUser(Arg.Any<User>());
    }
}

Rhino Mocks

Características:

Limitaciones:

Ejemplo sobre el caso de uso:

using Rhino.Mocks;
using Xunit;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var userRepositoryMock = MockRepository.GenerateMock<UserRepository>();
        var registerUser = new RegisterUser(userRepositoryMock);
        var userToRegister = new User { /* User Data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        userRepositoryMock.AssertWasCalled(repo => repo.SaveUser(Arg<User>.Is.Anything), opt => opt.Repeat.Once());
    }
}

FakeItEasy

Características:

Limitaciones:

Ejemplo sobre el caso de uso:

using FakeItEasy;
using Xunit;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var userRepositoryMock = A.Fake<UserRepository>();
        var registerUser = new RegisterUser(userRepositoryMock);
        var userToRegister = new User { /* User Data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        A.CallTo(() => userRepositoryMock.SaveUser(A<User>.Ignored)).MustHaveHappenedOnceExactly();
    }
}

Xunit.Mocks

Xunit.Mocks es una extensión de xUnit.NET y no una biblioteca de mocking independiente. Su uso se limita a proyectos que ya utilizan xUnit.NET como marco de pruebas unitarias.

Ejemplo sobre el caso de uso:

using Xunit;
using Xunit.Mocks;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var userRepositoryMock = new Mock<UserRepository>();
        var registerUser = new RegisterUser(userRepositoryMock.Object);
        var userToRegister = new User { /* User Data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        userRepositoryMock.Verify(repo => repo.SaveUser(It.IsAny<User>()), Times.Once);
    }
}

Castle Windsor

Características:

Limitaciones:

Ejemplo sobre el caso de uso:

using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Xunit;

public class RegisterUserShould
{
    [Fact]
    public void saveValidUser()
    {
        // Arrange
        var container = new WindsorContainer();
        var userRepositoryMock = container.Register(Component.For<UserRepository>().Instance(MockRepository.GenerateMock<IUserRepository>()));
        var registerUser = new RegisterUser(container.Resolve<UserRepository>());
        var userToRegister = new User { /* User Data */ };

        // Act
        registerUser.execute(userToRegister);

        // Assert
        userRepositoryMock.Verify(repo => repo.SaveUser(Arg<User>.Is.Anything), opt => opt.Repeat.Once());
    }
}

Comparativa

Facilidad de Uso

Moq: Se destaca por su sintaxis sencilla y su enfoque en LINQ. NSubstitute: Tiene una sintaxis muy intuitiva y es fácil de aprender. Rhino Mocks: Aunque poderoso, puede resultar más complejo para los principiantes. FakeItEasy: Diseñado para ser fácil de usar, con un enfoque en la simplicidad. Castle Windsor: Más conocido como un contenedor de inyección de dependencias, pero también ofrece capacidades de simulación.

Funcionalidades

Moq y NSubstitute: Ambos ofrecen una amplia gama de funcionalidades para cubrir la mayoría de las necesidades de simulación. Rhino Mocks: Proporciona características avanzadas pero su desarrollo ha sido menos activo recientemente. FakeItEasy: Ofrece un buen equilibrio entre funcionalidades y simplicidad. Castle Windsor: Su enfoque principal no está en la simulación, por lo que puede carecer de algunas funcionalidades específicas.

Comunidad y Soporte

Moq y NSubstitute: Tienen comunidades activas y un buen nivel de soporte. Rhino Mocks: Menos actividad reciente en la comunidad y en el desarrollo. FakeItEasy: Comunidad en crecimiento y buen soporte. Castle Windsor: Fuerte comunidad centrada principalmente en la inyección de dependencias.

Rendimiento

Las diferencias de rendimiento pueden ser mínimas para la mayoría de las aplicaciones, pero Moq y NSubstitute generalmente se consideran muy eficientes.

Conclusiones

Moq y NSubstitute son excelentes opciones generales, equilibrando facilidad de uso y funcionalidades. Rhino Mocks puede ser una buena opción para casos de uso específicos o para aquellos familiarizados con su sintaxis. FakeItEasy es ideal para quienes buscan simplicidad y facilidad de uso. Castle Windsor es preferible si también se necesita un contenedor de inyección de dependencias robusto, aunque puede no ser la mejor opción para la simulación exclusiva. Cada una de estas bibliotecas tiene sus fortalezas, y la elección depende en gran medida de las necesidades específicas del proyecto y la familiaridad del equipo con la biblioteca.