aspnet-core-mocking-moq-nsubstitute-unit-testing

Mocking in .NET with Moq and NSubstitute

  • 5 min

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:

  1. Moq: one of the most well-known. Syntax is somewhat more explicit, but very powerful.
  2. 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 Moq
Copied!

The 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();
    }
}
Copied!

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);
    }
}
Copied!

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);
    });
}
Copied!

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);
}
Copied!

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 .Object
Copied!

It’s a matter of preference. Moq is more explicit (“Setup”), NSubstitute is more automatic.

Common Errors When Creating Mocks

  1. Mocking concrete classes: Moq can work with interfaces and virtual members of non-sealed classes. Interfaces usually make the contract clearer, but don’t create them just to satisfy the mocking library.
  2. Mocking DbContext: trying to mock Entity Framework’s DbContext is 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.
  3. Mocking DTOs: Don’t mock data objects (User, Product). Simply use new Usuario(). Only mock behavior (Services).