Moq vs NSubstitute: .NET Mocking Libraries Compared

Mocking libraries let you create test doubles — stand-ins for dependencies that your code under test interacts with. In .NET, the two most popular options are Moq and NSubstitute. They solve the same problem but take different approaches to API design. Let's compare them where it counts.

Setting Up a Mock

Consider a service that depends on an IEmailSender interface:

Example.cs
public interface IEmailSender
{
    Task SendAsync(string to, string subject, string body);
    Task<bool> ValidateAddressAsync(string email);
}

Moq

Moq uses a Mock<T> wrapper class. You set up expectations on it, then extract the .Object property to get the actual interface implementation.

Example.cs
var mock = new Mock<IEmailSender>();
mock.Setup(x => x.ValidateAddressAsync(It.IsAny<string>()))
    .ReturnsAsync(true);

var service = new NotificationService(mock.Object);

NSubstitute

NSubstitute creates substitutes directly — no wrapper object needed. The API reads like natural method calls.

Example.cs
var emailSender = Substitute.For<IEmailSender>();
emailSender.ValidateAddressAsync(Arg.Any<string>())
    .Returns(true);

var service = new NotificationService(emailSender);

The difference is immediately visible. NSubstitute gives you the interface directly, while Moq requires you to work with a Mock<T> proxy and remember to use .Object when passing it around.

Argument Matching

Both libraries support flexible argument matching, but the syntax differs:

Example.cs
// Moq
mock.Setup(x => x.SendAsync(
    It.Is<string>(s => s.Contains("@")),
    It.IsAny<string>(),
    It.IsNotNull<string>()));

// NSubstitute
emailSender.SendAsync(
    Arg.Is<string>(s => s.Contains("@")),
    Arg.Any<string>(),
    Arg.Is<string>(s => s != null));

Both are equally capable here. NSubstitute uses Arg.Is and Arg.Any rather than Moq's It.Is and It.IsAny. Functionally identical; stylistically a matter of preference.

Verifying Calls

After running the code under test, you often need to verify that certain methods were called.

Moq

Example.cs
[Fact]
public async Task RegisterUser_SendsWelcomeEmail()
{
    var mock = new Mock<IEmailSender>();
    mock.Setup(x => x.ValidateAddressAsync(It.IsAny<string>()))
        .ReturnsAsync(true);

    var service = new UserService(mock.Object);
    await service.RegisterAsync("[email protected]", "Alice");

    mock.Verify(x => x.SendAsync(
        "[email protected]",
        It.Is<string>(s => s.Contains("Welcome")),
        It.IsAny<string>()),
        Times.Once);
}

NSubstitute

Example.cs
[Fact]
public async Task RegisterUser_SendsWelcomeEmail()
{
    var emailSender = Substitute.For<IEmailSender>();
    emailSender.ValidateAddressAsync(Arg.Any<string>())
        .Returns(true);

    var service = new UserService(emailSender);
    await service.RegisterAsync("[email protected]", "Alice");

    await emailSender.Received(1).SendAsync(
        "[email protected]",
        Arg.Is<string>(s => s.Contains("Welcome")),
        Arg.Any<string>());
}

NSubstitute's Received() reads more naturally than Moq's Verify() — you call the method on the substitute itself, which feels like reading the interaction you expect. Moq's approach, while functional, is more verbose with its lambda-based verification.

Throwing Exceptions

Example.cs
// Moq
mock.Setup(x => x.SendAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
    .ThrowsAsync(new SmtpException("Connection failed"));

// NSubstitute
emailSender.SendAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
    .ThrowsAsync(new SmtpException("Connection failed"));

Returning Different Values Per Call

Example.cs
// Moq - sequential returns
mock.SetupSequence(x => x.ValidateAddressAsync(It.IsAny<string>()))
    .ReturnsAsync(true)
    .ReturnsAsync(false);

// NSubstitute - sequential returns
emailSender.ValidateAddressAsync(Arg.Any<string>())
    .Returns(true, false);

NSubstitute's syntax is more concise here — just pass multiple values to Returns.

In August 2023, Moq version 4.20 included a package called SponsorLink that collected developer email hashes at build time. The community reaction was strongly negative, and SponsorLink was removed in version 4.20.2. While the immediate issue was resolved, the incident led many teams to evaluate alternatives. NSubstitute saw a significant uptick in adoption as a result.

Key Differences

Feature Moq NSubstitute
API style Wrapper (Mock<T>) Direct substitute
Readability Verbose lambdas Fluent, natural
Strict mode MockBehavior.Strict Substitute.For with Received checks
Community trust Damaged by SponsorLink Stable
Performance Comparable Comparable

Which Should You Choose?

NSubstitute if you're starting a new project. Its API is cleaner, more readable, and requires less ceremony. The library has a strong track record of stability.

Moq if your existing codebase already uses it and migration isn't worth the effort. Moq is still a capable library and the SponsorLink issue has been resolved.

Both libraries ultimately generate the same kind of dynamic proxy objects under the hood. The decision is primarily about developer experience and trust.