Moq vs NSubstitute: Choosing the Right Mocking Framework for Modern .NET Projects

 


Introduction

Mocking frameworks are one of those tools that quietly shape the quality of your test suite. You don’t think about them much—until your tests become brittle, unreadable, or painful to refactor.

For many years, Moq has been the default choice in the .NET ecosystem. It’s powerful, expressive, and deeply integrated with expression trees. However, over the last decade, NSubstitute has steadily gained popularity as teams look for simpler, more readable, and more refactor-friendly tests.

If you’re starting a new .NET project today, or reconsidering your current testing approach, this question comes up often:

Should I still use Moq, or is NSubstitute the better choice now?

This article explores that question in depth, with real-world code examples, trade-offs, and guidance based on modern development practices.


A Quick Overview

Moq

NSubstitute

Neither is “wrong”. But they encourage very different testing styles.

Philosophy: Power vs Intent

Moq’s Philosophy

Moq is designed to be explicit and powerful. You describe exactly what should happen, how often it should happen, and under what conditions.

This power is great, but it comes with ceremony.

NSubstitute’s Philosophy

NSubstitute aims to make tests read like specifications:

  • “When this happens, return that”

  • “This method should have been called”

  • “This should not have happened”

Less configuration, fewer moving parts, more focus on what rather than how.

Basic Setup Comparison

Scenario

We’re testing a service that depends on a repository.

public interface IUserRepository
{
    User GetById(Guid id);
}

public class UserService
{
    private readonly IUserRepository _repo;
    public UserService(IUserRepository repo)
    {
        _repo = repo;
    }
    public string GetDisplayName(Guid id)
    {
        var user = _repo.GetById(id);
        return user == null ? "Unknown" : user.Name;
    }
}

Moq: Basic Example

var repoMock = new Mock<IUserRepository>(); repoMock .Setup(r => r.GetById(It.IsAny<Guid>())) .Returns(new User { Name = "Alice" }); var service = new UserService(repoMock.Object); var result = service.GetDisplayName(Guid.NewGuid()); Assert.Equal("Alice", result);

Observations

  • Explicit setup using expression trees

  • Requires Mock<T> wrapper

  • Clear, but verbose


    NSubstitute: Basic Example

    var repo = Substitute.For<IUserRepository>(); repo.GetById(Arg.Any<Guid>()) .Returns(new User { Name = "Alice" }); var service = new UserService(repo); var result = service.GetDisplayName(Guid.NewGuid()); result.ShouldBe("Alice");

    Observations

    • No wrapper object

    • Reads closer to natural language

    • Fewer concepts to learn

    Verifying Calls

    Moq Verification

    repoMock.Verify( r => r.GetById(It.IsAny<Guid>()), Times.Once);

    This is powerful, but:

    • Requires Times

  • Encourages call-count assertions everywhere


  • NSubstitute Verification

    repo.Received(1) .GetById(Arg.Any<Guid>());

    Or simply:

    repo.Received() .GetById(Arg.Any<Guid>());

    Why this matters

    NSubstitute’s API nudges you away from over-verification.
    You assert behaviour only when it matters.


    Handling “Should Not Be Called”

    Moq

    repoMock.Verify( r => r.Delete(It.IsAny<Guid>()), Times.Never);

    NSubstitute

    repo.DidNotReceive() .Delete(Arg.Any<Guid>());

    This reads almost exactly like English, and that matters when scanning tests in PRs.


    Argument Matching

    Moq

    repoMock.Setup(r => r.GetById(It.Is<Guid>(id => id != Guid.Empty)) );

    NSubstitute

    repo.GetById(Arg.Is<Guid>(id => id != Guid.Empty));

    Both are expressive, but NSubstitute avoids expression-tree complexity.


    Async Methods

    Moq

    repoMock .Setup(r => r.GetAsync(It.IsAny<Guid>())) .ReturnsAsync(user);

    NSubstitute

    repo.GetAsync(Arg.Any<Guid>()) .Returns(Task.FromResult(user));

    Both are fine today, but NSubstitute keeps the API surface smaller.


    Strict Mocks vs Forgiving Defaults

    Moq Strict Mode

    var mock = new Mock<IUserRepository>(MockBehavior.Strict);

    Pros:

    • Forces explicit expectations

    Cons:

    • Adding a new dependency call breaks many tests

    • High maintenance cost

    NSubstitute Defaults

    • Unconfigured calls return defaults

    • Encourages testing outcomes, not implementation


    Refactoring Experience

    This is where teams feel the biggest difference.

    With Moq

    • Rename a method → broken setups

    • Change overloads → runtime exceptions

    • Subtle failures when expressions no longer match

    With NSubstitute

    • Compiler errors appear earlier

    • Fewer runtime surprises

    • Tests break where intent changes, not implementation details


    Over-Mocking and Test Smell

    Moq’s power makes it easy to:

    This often leads to:

    NSubstitute doesn’t prevent this—but it doesn’t encourage it either.


    When Moq Is Still a Good Choice

    Choose Moq if:

    • Your team already uses it heavily

    • You rely on expression-tree-heavy setups

    • You need very precise interaction verification

    • You’re maintaining a large existing codebase


    When NSubstitute Is the Better Default

    Choose NSubstitute if:


    Verdict

    For new .NET projects in 2025:

    NSubstitute is usually the better default choice.

    It produces tests that:

    • are easier to read

    • are easier to maintain

    • break less during refactoring

    • communicate intent clearly

    Moq is still powerful, but power comes at a cost.

Comments

Popular posts from this blog

Using a Semantic Model as a Reasoning Layer (Not Just Metadata)

A Thought Experiment: What If Analytics Models Were Semantic, Not Structural?

Dev Tunnels with Visual Studio 2022 and Visual Studio 2026