Update August 9, 2023:
As of August 8, 2023 (moq 4.20), the library includes a .dll that sends your data (at least your email) to sponsorlink. Note: I am in the process of creating a post about nsubstitute or fakeit.
Summary video:
Table of Contents
1 - What is a mock in C#
In this post, we’re going to look at a more advanced topic within testing, which is creating a mock
of the interfaces that access data or third-party services, so we can properly test our processes.
The term mock technically means to ridicule or make fun of, which doesn’t make a lot of sense in this context. However, one of its synonyms is “mimic” , which means to imitate, and that’s what we’re talking about here.
Therefore, in this post, we’ll look at how to imitate those interfaces that give us access to data, so we can test the program flow.
2 - System Definition
For this example, I reused part of an interview from a while ago, where I was asked to create an application (an API) with two endpoints: one to insert values into a database and another to read from it.
Here you can see a diagram of what the application will look like at the end.
If we get closer to the code, we have several parts:
- API
- Domain Layer
- Data Access Layer
- DTOs
- Tests
Our mock will be simulating the interface we have in the data access layer.
This means all the domain code (the logic), except for the actual database insert, will be tested.
The main code we have is as follows:
- First, the DTO, which contains the
Articulo
object
public class Articulo
{
public int Id { get; set; }
public string Contenido { get; set; }
public string Titulo { get; set; }
public DateTime Fecha { get; set; }
public int AutorId { get; set; }
}
- We have our API with two endpoints, which only call the domain (service) we’re going to test.
public class ArticulosController : ApiController
{
private readonly ArticulosServicio _articulosServicio;
public ArticulosController(ArticulosServicio articulosServicio)
{
_articulosServicio = articulosServicio;
}
[HttpPost]
[Route("Articulo")]
public int InsertarArticulo(Articulo articulo)
{
var resultado = _articulosServicio.InsertarArticulo(contenido, titulo, autor);
return resultado;
}
[HttpGet]
[Route("Articulo/{id}")]
public Articulo ConsultarArticulo(int id)
{
var resultado = _articulosServicio.ConsultarArticulo(id);
return resultado;
}
}
As we can see, there’s one endpoint to insert articles and another to read a specific article, but this functionality is NOT inside our unit test.
Note: All code is synchronous , for better performance, we should use asynchronous calls, but we’ll cover that later on.
- The domain layer, which contains the logic and is the part we’ll test:
namespace Dominio.Service
{
public class ArticulosServicio
{
private readonly IAutorRepository autorRepository;
private readonly IArticulosRepository articuloRepository;
public Articulo InsertarArticulo(string contenido, string titulo, int autorId)
{
if (!autorRepository.AutorValido(autorId))
{
throw new Exception("Autor not valido");
}
var articuloId = articuloRepository.InsertarArticulo(contenido, titulo, autorId);
return ConsultarArticulo(articuloId);
}
public Articulo ConsultarArticulo(int id)
{
return articuloRepository.GetArticulo(id);
}
}
}
- The data access layer with interfaces and repositories:
public interface IAutorRepository
{
Autor GetAutor(int id);
bool AutorValido(int id);
}
public interface IArticulosRepository
{
int InsertarArticulo(string contenido, string titulo, int autorId);
Articulo GetArticulo(int id);
}
public class AutorRepository : IAutorRepository
{
public bool AutorValido(int id)
{
throw new NotImplementedException();
}
public Autor GetAutor(int id)
{
throw new NotImplementedException();
}
}
public class ArticulosRepository : IArticulosRepository
{
public Articulo GetArticulo(int id)
{
throw new NotImplementedException();
}
public int InsertarArticulo(string contenido, string titulo, int autorId)
{
throw new NotImplementedException();
}
}
As we can see, the repositories are NOT implemented, so if we try to access them, an exception will be thrown.
3 - Why Simulate Code Using Mock in Unit Tests
This is a crucial point to keep in mind: the definition of a unit test means we should test methods or processes individually.
But, of course, those processes might call external services or the database itself.
If we also want to test inserting into the database, we need to perform integration tests, which, as we remember from the previous post, are much more complicated to create and longer to execute.
4 - Installing a Mock Library
To implement mocking in our test code, we have to use one of the existing libraries, since there is none included by default with the system.
So, we go to the nuget package
manager and install Moq
or run Install-Package Moq
in the terminal.
With this simple step, we can now simulate our call to the repository.
5 - Using the Mock Library
First of all, we need to be clear on how to simulate our calls to repositories. So, we must declare the interfaces we’re going to simulate with the moq library.
Mock<IArticulosRepository> articuloRepo = new Mock<IArticulosRepository>();
Mock<IAutorRepository> autorRepo = new Mock<IAutorRepository>();
To do so, we use the .Setup()
method on our mock.
autorRepo.Setup(a => a.AutorValido(autorId)).
As you can see, you need to specify which method you want to simulate, as well as the parameters the method will receive. In this case, we have several options.
- We can use the
It.IsAny<T>()
class, which means it accepts any object of that type.autorRepo.Setup(a => a.AutorValido(It.IsAny<int>()))
- We can specify a specific object, meaning that the object sent to the method should have those exact values.
autorRepo.Setup(a => a.AutorValido(autorId)).
Finally, we specify the result of the call using the .Returns
method. This result is an object of the type the function should return, and we need to create it in our test class.
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
});
To pass the simulated object we’ve created, we need to send the mock to the class we’re going to use. For this, we use the mock.Object
property.
ArticulosServicio servicio = new ArticulosServicio(articuloRepo.Object, autorRepo.Object);
As we can see, mock.Object
is of the type for which we are creating the simulation.
If we run the test and debug, if we stop the code right after the repository call, it returns the value we set.
Finally, we should verify that our call was executed, since we’re supposed to only mock what we need.
To do that, we use the .Verify()
method, which checks, with the parameters we include in the delegate, if we’re making the correct call.
Assert.AreEqual(autorId, articulo.Autor.AutorId);
articuloRepo.Verify(a => a.GetArticulo(1));
articuloRepo.Setup(a => a.InsertarArticulo(contenido, titulo, autorId));
autorRepo.Setup(a => a.AutorValido(It.IsAny<int>()));
Conclusion
- In this post we have seen what mocking/moq is and how to use it.
- Using mocks is essential in our daily work, as we’ll need this functionality to simulate calls to our dependencies.
- The key methods and properties are:
- The
.Setup()
method with its.Return()
- The
.Object
property - The
.Verify()
method, which checks if the call was executed.
- The
If there is any problem you can add a comment bellow or contact me in the website's contact form