What it is
NSubstitute is a mocking library for .NET with a deliberately minimal syntax, aiming to read as natural language rather than as configuration.
Create a substitute for an interface, configure returns with Returns, and assert with Received. There is no separate mock object to unwrap.
Installation
dotnet add package NSubstituteGetting started
The smallest useful thing you can do with it, and what each part means.
csharp
var repo = Substitute.For<IBookRepository>();
// No lambda — call the method and state what it returns.
repo.GetAsync(42, Arg.Any<CancellationToken>())
.Returns(new Book { Id = 42, Title = "Dune" });
var service = new BookService(repo); // no .Object needed
var title = await service.GetTitleAsync(42);
Assert.Equal("Dune", title);
await repo.Received(1).GetAsync(42, Arg.Any<CancellationToken>());
await repo.DidNotReceive().DeleteAsync(Arg.Any<int>());Advanced usage
Where the library earns its place over a simpler alternative.
csharp
// Successive calls return different values.
repo.NextAsync().Returns(first, second, null);
// Compute the return from the arguments.
repo.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(call => new Book { Id = call.Arg<int>() });
// Simulate a failure.
repo.SaveAsync(Arg.Any<Book>()).Returns<Task>(_ => throw new DbException());
// Capture arguments for a detailed assertion.
await repo.Received().SaveAsync(Arg.Is<Book>(b => b.Title == "Dune" && b.Year == 1965));Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Received() call did not match
- The message prints the actual calls received. Compare them against your matcher — usually a CancellationToken or a default parameter differs.
- Cannot substitute for a non-virtual member
- NSubstitute can only intercept interfaces and virtual members. Extract an interface for the type under test.
Best practices
- Use Arg.Is with a predicate rather than Arg.Any when the argument matters.
- Assert with Received(n) to pin down call counts, not just that a call happened.
- Substitute interfaces you own; mocking third-party clients couples tests to their design.
- Prefer real dependencies via Testcontainers for data access rather than substituting a repository.
Background
Why it exists, and what it was reacting to.
NSubstitute was designed as a reaction to Moq's lambda-heavy setup calls. There is one entry point — Substitute.For<T>() — and configuration reads as an assignment.
