Moq vs NSubstitute: Test Real .NET Consumers and Understand Proxy Limits
Quick answer: Moq and NSubstitute both support interface-based test doubles. Moq offers an explicit strict mode; NSubstitute emphasizes concise setup and interaction checks. Choose for test readability and the behavior you need to verify, rather than an unsupported claim that one is always faster.
Test the consumer, not just the mock
A test that configures a mock and immediately calls that same mock establishes little about your application. Put the dependency behind an interface, pass the test double into the real consumer, then assert the consumer’s result. This example has a small consumer whose behavior would change if it stopped using the requested user ID:
public interface IUserDirectory
{
string GetName(int id);
}
public sealed class GreetingService(IUserDirectory directory)
{
public string Greet(int id) => $"Hello, {directory.GetName(id)}";
}
The consumer is deliberately small so the difference between mocking libraries stays visible. In a real test suite, also check meaningful failure and edge cases, and prefer a simple fake where it makes the test clearer. Not every dependency requires a dynamic mocking library.
The same behavior with Moq and NSubstitute
With xUnit and Moq:
using Moq;
using Xunit;
public class MoqGreetingTests
{
[Fact]
public void GreetsTheRequestedUser()
{
var directory = new Mock<IUserDirectory>(MockBehavior.Strict);
directory.Setup(x => x.GetName(7)).Returns("Alice");
var service = new GreetingService(directory.Object);
Assert.Equal("Hello, Alice", service.Greet(7));
directory.Verify(x => x.GetName(7), Times.Once);
}
}
With xUnit and NSubstitute:
using NSubstitute;
using Xunit;
public class NSubstituteGreetingTests
{
[Fact]
public void GreetsTheRequestedUser()
{
var directory = Substitute.For<IUserDirectory>();
directory.GetName(7).Returns("Alice");
var service = new GreetingService(directory);
Assert.Equal("Hello, Alice", service.Greet(7));
directory.Received(1).GetName(7);
}
}
These are two alternatives for testing the same consumer. Install the library used by your test project; the setup call supplies the dependency’s response, and the assertion tests the real service’s output.
Know the proxy limitations
Both libraries can substitute interfaces. For class-based doubles, ordinary non-virtual methods cannot be intercepted by these proxy-based approaches. With NSubstitute, a non-virtual class member can execute real code. Do not attempt to make a database or network call safe merely by constructing a substitute of a concrete class.
Moq’s MockBehavior.Strict rejects unexpected invocations without a matching setup. NSubstitute’s Received checks interactions after execution; that is not the same as an equivalent global strict mode. Automatic or recursive return values should also be understood before relying on them.
Choose for a maintainable test suite
Use strict expectations where an unexpected call represents an actual defect. Over-specifying every internal call makes tests fragile during refactoring. Prefer assertions on observable outcomes, and verify interactions when they are part of the behavior—for example, sending one notification or persisting a change.
Keep unit tests isolated, then cover real serialization, SQL queries and remote integrations with appropriately scoped integration tests. A perfectly configured mock cannot prove that the production dependency understands the same request.
References
Moq Quickstart, NSubstitute: creating a substitute, received calls, and automatic return values.