A mock is a configurable test double that allows you to observe how a dependency is used.
In the previous article, we tested a simple calculator. Now we’ll work with services that call repositories, external APIs, and file systems.
The problem is that a Unit Test must be isolated.
- If your test tries to connect to SQL Server, it’s no longer a unit test (it’s an integration test).
- If your test fails because the WiFi went down, it’s a bad test.
- If your test takes 2 seconds to run because it executes a slow query, no one will want to run the tests.
How do we test the logic of a service (UsuarioService) without actually executing the database (UsuarioRepository)?
The answer is to use test doubles. A stub returns prepared responses; a mock also allows verifying interactions. Libraries like Moq allow doing both with the same object.
What is a Mock
A Mock is a simulated object that mimics the behavior of a real interface in a controlled way.
In an action movie, the protagonist doesn’t jump off the building; a stunt double does.
- Interface:
IJumpBuildings(The contract: “we must jump”). - Real Implementation: The famous actor (Expensive, delicate, if they get injured filming stops).
- Mock: the test double. It does what you tell it to do for that specific test.
In our code:
- We don’t inject the real
SqlUsuarioRepository. - We inject a
Mock<IUsuarioRepository>to which we say: “When you’re asked for user 1, return this. Don’t ask, just do it”.
Moq and NSubstitute
In .NET, there are two major libraries for this:
- Moq: one of the most well-known. Syntax is somewhat more explicit, but very powerful.
- NSubstitute: an alternative with a more fluent and readable syntax.
In this article, we will use Moq because it is very common in .NET projects, but the theory applies to both.
Installation
dotnet add package MoqThe Scenario We’re Going to Test
We have a service that looks up a user in the repository. If the user doesn’t exist, it throws an error. If they exist, it returns their name in uppercase.
// The Interface (The Dependency)
public interface IUsuarioRepository
{
Usuario? GetById(int id);
}
// The Class to Test (SUT: System Under Test)
public class UsuarioService
{
private readonly IUsuarioRepository _repo;
public UsuarioService(IUsuarioRepository repo)
{
_repo = repo;
}
public string ObtenerNombreMayusculas(int id)
{
var usuario = _repo.GetById(id); // 👈 External Dependency
if (usuario == null) throw new KeyNotFoundException("User does not exist");
return usuario.Nombre.ToUpper();
}
}Creating the Test with Moq
Let’s test the “happy path”: The repository finds the user.
using Moq;
using Xunit;
public class UsuarioServiceTests
{
[Fact]
public void GetName_IfUserExists_ReturnsNameInUppercase()
{
// 1. ARRANGE (Prepare the scenario)
// Create the mock of the interface
var mockRepo = new Mock<IUsuarioRepository>();
// CONFIGURATION (Setup): Give the mock its script
// "When someone calls GetById(1), return a user named 'Luis'"
mockRepo.Setup(repo => repo.GetById(1))
.Returns(new Usuario { Id = 1, Nombre = "Luis" });
// Inject the simulated OBJECT (.Object) into our service
var servicio = new UsuarioService(mockRepo.Object);
// 2. ACT (Action)
var resultado = servicio.ObtenerNombreMayusculas(1);
// 3. ASSERT (Verification)
Assert.Equal("LUIS", resultado);
}
}Notice the mechanism.
We didn’t need a database. UsuarioService thinks it talked to the repository, but it actually talked to our lying object that returned “Luis” instantly.
Testing Errors and Generic Arguments
What if we want to test that it throws an exception when the user doesn’t exist? Also, what if we want the Mock to respond to any ID, not just 1?
We use It.IsAny<T>().
[Fact]
public void GetName_IfUserDoesNotExist_ThrowsException()
{
// Arrange
var mockRepo = new Mock<IUsuarioRepository>();
// Configuration: "Call it with any integer, I'll return null"
mockRepo.Setup(x => x.GetById(It.IsAny<int>()))
.Returns((Usuario?)null);
var servicio = new UsuarioService(mockRepo.Object);
// Act & Assert
Assert.Throws<KeyNotFoundException>(() =>
{
servicio.ObtenerNombreMayusculas(999);
});
}Verifying Behavior with Verify
Sometimes we don’t care about what the method returns, but rather whether a method was called.
Example: “If I delete a user, make sure the Delete method of the repository was called”.
[Fact]
public void DeleteUser_CallsRepositoryCorrectly()
{
// Arrange
var mockRepo = new Mock<IUsuarioRepository>();
var servicio = new UsuarioService(mockRepo.Object);
// Act
servicio.Borrar(5);
// Assert: Verify that the Delete(5) method was called EXACTLY once
mockRepo.Verify(repo => repo.Delete(5), Times.Once);
}This is extremely powerful for testing side effects (sending emails, writing to logs, etc.).
The Alternative: NSubstitute
Just to show you the syntactical difference. NSubstitute aims to read more like natural language, avoiding .Setup() and .Object.
// With NSubstitute
var repo = Substitute.For<IUsuarioRepository>();
// Configuration
repo.GetById(1).Returns(new Usuario { Nombre = "Luis" });
// Usage
var servicio = new UsuarioService(repo); // No need for .ObjectIt’s a matter of preference. Moq is more explicit (“Setup”), NSubstitute is more automatic.
Common Errors When Creating Mocks
- Mocking concrete classes: Moq can work with interfaces and
virtualmembers of non-sealed classes. Interfaces usually make the contract clearer, but don’t create them just to satisfy the mocking library. - Mocking DbContext: trying to mock Entity Framework’s
DbContextis often fragile. For business logic, mock your own interface. To test real queries, use integration tests with an in-memory SQLite database, a throwaway database, or Testcontainers. - Mocking DTOs: Don’t mock data objects (User, Product). Simply use
new Usuario(). Only mock behavior (Services).