This is another post within the group of posts I have about testing.
In previous posts we saw what it is and how to create mocks, but this is not our only option when we want to simulate functionalities or service responses.
In this post we will see the different options and their details
Table of Contents
1 - What are test doubles?
Honestly, I don't even know how to properly call what I'm going to explain in English. But basically, creating a double means writing code that acts in a particular way, which is different from the original implementation.
There are several ways to achieve the result we expect, and in this post, we will see them.
In our case, we have code that inserts into a database and this code is behind an interface called IArticulosRepository
.
public interface IArticulosRepository
{
int InsertarArticulo(string contenido, string titulo, int autorId);
Articulo GetArticulo(int id);
}
2 - What is a mock?
The video we saw about mock went into a lot of detail, and I don't want to repeat myself.
But to sum it up, it’s about adding configuration in the test so that certain methods act in a specific way. This configuration is set at the individual test level. Obviously, you can program it so that several tests have this configuration, but normally they are individual. In C#, we usually use the Moq
library.
Here you can see an example, with the previous interface.
Mock<IArticulosRepository> articuloRepo = new Mock<IArticulosRepository>();
Mock<IAutorRepository> autorRepo = new Mock<IAutorRepository>();
autorRepo.Setup(a => a.AutorValido(It.IsAny<int>())).Returns(true);
articuloRepo.Setup(a => a.InsertarArticulo(contenido, titulo, autorId)).Returns(1);
articuloRepo.Setup(a => a.GetArticulo(1)).Returns(new Articulo()
{
Autor = new Autor()
{
AutorId = autorId,
Nombre = "test"
},
Contenido = contenido,
Fecha = DateTime.UtcNow,
Id = 1,
Titulo = titulo
});
We have to define the response of each method, including the parameters. For example, if we know the code will query IDs 1 and 2, we need to mock GetArticulos
for 1 and 2.
Note: it can be configured so that GetArticulos(It.IsAny<Int>).return(..)
returns the same result for both, but that's not usually the norm.
If you want more details about mocks, I recommend you take a look at my post about mocks in unit tests.
3 - What is a Stub in tests?
The second way we can create a double is by creating a stub
.
Creating a stub means creating a class that always returns the same thing.
For example, in our case, we implement the interface in a class and always return the same information except for the id, which is the one we pass as a parameter to the method:
public class ArticulosStub : IArticulosRepository
{
public int InsertarArticulo(string contenido, string titulo, int autorId)
{
return 1;
}
public Articulo GetArticulo(int id)
{
return new Articulo()
{
Autor = new Autor()
{
AutorId = 1,
Nombre = "test"
},
Contenido = "contenido",
Fecha = DateTime.UtcNow,
Id = id,
Titulo = "titulo"
};
}
}
This means, it doesn't matter if a particular id exists or not, the information from GetArticulos
will always be "correct".
4 - What is a Fake in tests?
Finally, let's see what a fake
is. A fake is simply a complete implementation of the interface, but it obviously only works within the tests. For example, in our case, when we call InsertarArticulo we can do it into a list and then when we run GetArticulo
we read it from the list.
public class FakeArticulos : IArticulosRepository
{
public List<Articulo> Articulos { get; set; }
public int currentIdentifier { get; private set; }
public FakeArticulos()
{
Articulos = new List<Articulo>();
currentIdentifier = 0;
}
public int InsertarArticulo(string contenido, string titulo, int autorId)
{
Articulo articulo = new Articulo()
{
Autor = new Autor()
{
AutorId = autorId,
Nombre = "test"
},
Contenido = contenido,
Fecha = DateTime.UtcNow,
Id = ++currentIdentifier,
Titulo = titulo
};
Articulos.Add(articulo);
return articulo.Id;
}
public Articulo GetArticulo(int id)
=> Articulos.FirstOrDefault(articulo => articulo.Id == id);
}
Creating fakes is not an easy task. Depending on the class you want to implement, it can be quite complex compared to creating a mock, for example.
5 - Mock vs Stub vs Fake, which one to choose?
It will depend on the situation, but personally, I recommend using mocks whenever possible. For example, to test a functionality or a use case, it is easier to reuse mocks when necessary than to implement everything with fakes, since in general it requires less maintenance.
Honestly, I don't really use stubs in the real world, for the same reason as before, it is easier to see and understand the functionality of a test with a stub
In real-world scenarios, fakes are usually made when we have tests that affect others or as part of a system that allows us or facilitates us to create integration tests. For example, if we have a microservices system and for asynchronous communication, instead of making calls with a producer-consumer pattern we simulate it in memory, and we also simulate those generated events.
So we should use whatever works best for each situation, but be careful with Fakes and duplicating functionalities, it can be a very deep rabbit hole.
If there is any problem you can add a comment bellow or contact me in the website's contact form