Mock en C# con Nsubstitute y FakeItEasy

En este post, más que una librería como tal, lo que voy a hacer es comparar múltiples librerías que nos permiten hacer mock dentro de C#. 

Ya vimos en otro post cual es la diferencia entre mock, doble y fake, así, que si no estás familiarizado con los términos te recomiendo que empieces con este post. 

 

Nota, debido a los recientes cambios (versión 4.20) en la librería moq, no vamos a cubrirla en este post. De todas formas tienes un post que la cubre de marzo de 2020;

En este post vamos a cubrir mock en .net con las librerias NSubstitute y FakeitEasy, La idea de este post es que nos sirva como introducción a dichas librerías, o si tu jefe os esta haciendo migrar de moq, que podais ver la sintaxis de los ejemplos más comunes. 

 

 

Recuerda que todo el código está disponible en GitHub.

 

1 - Qué es mock y por qué utilizarlo?

En esta publicación explicaremos una característica muy común dentro de las pruebas unitarias o los unit test. Se trata de simular interfaces que se conectan con datos externos o servicios de terceros. Este proceso nos permitirá realizar pruebas exhaustivas en nuestros procedimientos.

 

El término "mock", en términos técnicos, tiene una connotación de imitación. Por lo tanto, en este artículo abordaremos la manera de imitar estas interfaces que facilitan el acceso a datos, posibilitando así la evaluación detallada del flujo del programa.

El uso de mock nos facilita la vida a la hora de cubrir todos los posibles escenarios dentro de nuestra aplicación.

 

Para este post, vamos a ver todos los ejemplos utilizando tanto la librería NSubstitute como la librería FakeItEasy.

 

2 - Caso de uso y librerías populares para hacer mock

Para ver el uso de mock en c# vamos a ver el siguiente caso de uso, una servicio que tiene dos dependencias, nos da igual lo que hagan esas dependencias ya que vamos a estar imitando su funcionamiento. 

public class ArticleService
{
    private readonly IAuthorRepository _authorRepository;
    private readonly IArticleRepository _articleRepository;

    public ArticleService(IAuthorRepository authorRepository, IArticleRepository articleRepository)
    {
        _authorRepository = authorRepository;
        _articleRepository = articleRepository;
    }

    public Article InsertArticle(string content, string title, int authorId)
    {
        if (!_authorRepository.IsValid(authorId))
        {
            throw new Exception("Author not valid");
        }
            
            
        int aritcleId = _articleRepository.Insert(content, title, authorId);

        return GetArticle(aritcleId);
    }

    public Article GetArticle(int id)
    {
        return _articleRepository.Get(id);
    }
}


public interface IAuthorRepository
{
    Author Get(int id);
    bool IsValid(int id);
}
public interface IArticleRepository
{
    int Insert(string content, string title, int authorId);
    Article Get(int id);
}

public record Article(int Id, string Content, string Title, DateOnly Date, int AuthorId);

public record Author(int Id, string Name);

Y para imitar el funcionamiento instalamos la librería, Nsubstitute  o FakeItEasy, como he dicho antes, tengo un post dedicado a la librería moq.

 

 

3 - Uso de mock con NSubstitute y FaketItEasy

El principal caso de uso de las librerías de mock siempre es simular el funcionamiento de las interfaces que abstraen las dependencias.

 

En el caso de uso del código de ejemplo son los interfaces que hacen referencia a dos repositorios

Para el primer ejemplo vamos a imitar y sobreescribir el funcionamiento del método Get dentro de IArticlerepository;

Ejemplo en FakeItEasy:

[Fact]
public void WhenArticleExist_ThenReturnArticle() //FakeIt easy
{
    Article article = new Article(1, "content", "title", DateOnly.FromDateTime(DateTime.UtcNow), 1);
    
    IAuthorRepository authorRepository = A.Fake<IAuthorRepository>();
    IArticleRepository articleRepository = A.Fake<IArticleRepository>();
    A.CallTo(() => articleRepository.Get(article.Id)).Returns(article);
    
    ArticleService service = new ArticleService(authorRepository, articleRepository);
    Article result = service.GetArticle(article.Id);

    Assert.Equal(article.Content, result.Content);
}

Como vemos es sencillo, simplemente utilizamos A como tipo (no se muy bien porque lo hicieron con A en vez de con Fake) y ya dentro de A tenemos acceso  a todo lo que necesitamos. 

Finalmente únicamente tenemos que pasar nuestra interfaz al constructor.

El uso de A.CallTo nos permite especificar el funcionamiento del método que queremos imitar.

  • Nota: si fuera un método asíncrono, el return cambia por A.CallTo(() => articleRepository.Get(article.Id)).Returns(Task.FromResult(article));.

 

Ejemplo en Nsubstitute:

[Fact]
public void WhenArticleExist_ThenReturnArticle() //Nsubstitute
{
    Article article = new Article(1, "content", "title", DateOnly.FromDateTime(DateTime.UtcNow), 1);

    IAuthorRepository authorRepository = Substitute.For<IAuthorRepository>();
    IArticleRepository articleRepository = Substitute.For<IArticleRepository>();
    articleRepository.Get(article.Id).Returns(article);
    
    ArticleService service = new ArticleService(authorRepository, articleRepository);
    Article result = service.GetArticle(article.Id);

    Assert.Equal(article.Content, result.Content);
}

Muy similar al caso anterior, únicamente debemos de cambiar un poquitin la sintaxis y ya estamos ahí. 

Algo que me gusta mucho de NSubstitute es que cuando haces el mock del método, únicamente necesitas pasar los valores correspondientes a dicho método, no tienes que incluir sintaxis adicional. 

 

3.1 - Ejemplo completo mock con Nsubstitue y FakeItEasy

Ahora vamos a pasar a un ejemplo algo más completo, o donde tenemos más información que comprobar. En este caso vamos a testear método InsertArticle, como vemos tiene dos salidas, así que tenemos que hacer dos tests, uno para cada una de las salidas. 

 

Por norma general a mi me gusta empezar por el happy path así que por ahí vamos a ir.

 

Este es el ejemplo con FakeItEasy:

[Fact]
public void WhenAuthorIsValid_ThenArticleIsInserted()
{
    Article article = new Article(1, "content", "title", DateOnly.FromDateTime(DateTime.UtcNow), 10);

    IAuthorRepository authorRepository = A.Fake<IAuthorRepository>();
    IArticleRepository articleRepository = A.Fake<IArticleRepository>();
    A.CallTo(() => authorRepository.IsValid(A<int>._)).Returns(true);
    A.CallTo(() => articleRepository.Insert(article.Content, article.Title, article.AuthorId)).Returns(article.Id);
    A.CallTo(() => articleRepository.Get(article.Id)).Returns(article);

    ArticleService service = new ArticleService(authorRepository, articleRepository);
    Article result = service.InsertArticle(article.Content, article.Title, article.AuthorId);

    A.CallTo(() => articleRepository.Insert(article.Content, article.Title, article.AuthorId))
        .MustHaveHappened(1, Times.Exactly);
    Assert.Equal(article.Content, result.Content);
}

En este ejemplo podemos ver como simplemente hemos incluido algo más de configuración para nuestro test, pero además hemos verificado que estamos haciendo la llamada en el assertion. Para ello utilizamos el método MustHaveHappened con el número de veces que queremos asegurarnos de que algo pasa. 

 

Ejemplo con Nsubstitute:

[Fact]
public void WhenAuthorIsValid_ThenArticleIsInserted()
{
    Article article = new Article(1, "content", "title", DateOnly.FromDateTime(DateTime.UtcNow), 10);

    IAuthorRepository authorRepository = Substitute.For<IAuthorRepository>();
    IArticleRepository articleRepository = Substitute.For<IArticleRepository>();
    articleRepository.Get(article.Id).Returns(article);
    authorRepository.IsValid(Arg.Any<int>()).Returns(true);
    articleRepository.Insert(article.Content, article.Title, article.AuthorId).Returns(article.Id);

    ArticleService service = new ArticleService(authorRepository, articleRepository);
    Article result = service.InsertArticle(article.Content, article.Title, article.AuthorId);

    articleRepository.Received(1).Insert(article.Content, article.Title, article.AuthorId);
    Assert.Equal(article.Content, result.Content);
}

Igual que en el anterior, el cambio es mínimo, esta vez configuramos el número de veces que hemos recibido una llamada con Received(times)

Es muy importante comprobar el número de veces que un método ocurre para asegurarnos de que no tenemos bugs y de que funciona como esperamos. 

 

 

Ah, una cosa a tener en cuenta, cuando queremos que un método devuelva algo, pero nos da igual el input, podemos configurar el mock para que así sea, si te fijas en los ejemplos anteriores ya hemos visto esta funcionalidad. En FakeItEasy lo hacemos con A<T>._ mientras que en NSubstitute lo hacemos con Arg.Any<T>().

 

Para comprobar el path que falla con la excepción lo tenemos que hacer ya bien sea con la propia librería de tests o como mostré en el post anterior con FluentAssertions


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café