Updated on
NSubstitute is a mocking library for .NET that reads like plain C#: Substitute.For<IEmailService>() creates the mock, service.IsValid(x).Returns(true) configures it, service.Received().Send(x) verifies it. No Mock<T> wrapper, no .Object property, no lambda ceremony.
That syntax is why many teams pick it over Moq. It works on interfaces and virtual members, and its analyzer package catches the cases it can’t fake at compile time.
What Is NSubstitute?
NSubstitute is an open-source mocking library for .NET unit tests. It creates substitute objects for our interfaces and virtual members, so we can test a class in isolation: the substitute stands in for a real dependency, returns the values we script, and records every call for verification.
Its defining trait is syntax. Where Moq wraps everything in Mock<T> and lambda-based Setup() calls, NSubstitute aims to read like the code under test: Substitute.For<IEmailService>() creates the fake, emailService.IsValidEmail(address).Returns(true) scripts behavior, and emailService.Received().SendEmail(...) asserts the interaction. There is no .Object to unwrap; the substitute is the instance we inject.
The trade-off behind the clean syntax: NSubstitute can only intercept interface members and virtual methods, and calling a scripted method really invokes it once during configuration. The companion NSubstitute.Analyzers.CSharp package turns most such mistakes into compiler warnings. NSubstitute is BSD-3-Clause licensed (not MIT, as is often assumed) and actively maintained; the current release is 6.2.0.
That limit is the library’s own, stated in its documentation: a substitute “can only work with virtual members of the class that are overridable in the test assembly” (NSubstitute docs, Creating a substitute, read 2026-08-09). Everything else on the class really runs.
Installing NSubstitute and Setting up Our Code
The first thing we have to do is install the NuGet package:
dotnet add package NSubstitute
It is also good to install NSubstitute.Analyzers.CSharp to help us catch potential problems with the usage of NSubstitute in our code:
dotnet add package NSubstitute.Analyzers.CSharp
This Roslyn analyzer helps us in such cases when we mock non-virtual members as mentioned in the previous section. If you want to know more about how Roslyn analyzer work, we have a separate article about the usage of Roslyn analyzers.
After that, we create a very simple User record:
public record User(string Name, string Email);
Next, let’s create an interface of a service that will send emails but without an implementation:
public interface IEmailService
{
bool IsValidEmail(string email);
bool SendEmail(string recipient, string subject, string message);
Task<bool> SendEmailAsync(string recipient, string subject, string message);
}
Then, we define the INotificationService interface:
public interface INotificationService
{
bool NotifyUser(User user, string message);
}
And finally, its implementation:
public class NotificationService : INotificationService
{
private readonly IEmailService _emailService;
public NotificationService(IEmailService emailService)
{
_emailService = emailService;
}
public bool NotifyUser(User user, string message)
{
if (!_emailService.IsValidEmail(user.Email) ||
string.IsNullOrWhiteSpace(message))
{
return false;
}
return _emailService
.SendEmail(user.Email, "Notification from CodeMaze", message);
}
}
The NotificationService class is implementing the INotificationService interface and uses an injected IEmailService to send email notifications.
How Do We Mock With NSubstitute in C#?
Mocking with NSubstitute follows the same three-step arrange-act-assert rhythm in every test. First, we create substitutes for the dependencies of the class under test with Substitute.For<T>() and pass them to its constructor, typically in the test class constructor so every test starts from a clean set.
Second, we script only the behavior the scenario needs: _emailService.IsValidEmail(_user.Email).Returns(true) makes the substitute answer that exact argument, while Arg.Any<string>() or ReturnsForAnyArgs() widen the match when the argument doesn’t matter.
Third, after invoking the method under test, we assert two things: the returned value, and the interactions. _emailService.Received(1).SendEmail(...) proves the call happened once, and DidNotReceive() proves an unwanted call never happened. That last step is what mocking adds over plain fakes: we verify our class’s conversation with its dependencies, not just its output. The same pattern scales unchanged from a two-line test to a service with half a dozen dependencies.
To properly test our NotificationService class, we need to be able to mock and have complete control over our IEmailService interface. We’ll see how we can achieve that by using NSubstitute.
Mocking an Object With NSubstitute
With NSubstitute, it is easy for us to mock an object:
public class NotificationServiceTests
{
private readonly User _user;
private readonly IEmailService _emailService;
private readonly NotificationService _notificationService;
public NotificationServiceTests()
{
_user = new User("Code-Maze", "[email protected]");
_emailService = Substitute.For<IEmailService>();
_notificationService = new NotificationService(_emailService);
}
}
We create the NotificationServiceTests class which is responsible for testing the NotificationService class. Within the test class, we create a User and a substitute/mock object of type IEmailService using NSubstitute’s Substitute.For<T>() method, which allows us to simulate the behavior of the actual IEmailService dependency.
We then inject the substitute into the constructor of NotificationService to create a new instance and assign it to _notificationService. By doing this, we can isolate the NotificationService class and verify its interactions with the substituted IEmailService during our testing.
Mocking Behaviors and Return Values With NSubstitute
For any mocking library, it’s vital to be able to mock behaviors and return values:
[Fact]
public void GivenInputIsCorrect_WhenNotifyUserIsInvoked_ThenTrueIsReturned()
{
// Arrange
const string message = "Mocking behaviors and expectations with NSubstitute";
_emailService.IsValidEmail(_user.Email)
.Returns(true);
_emailService.SendEmail(_user.Email, "Notification from CodeMaze", message)
.Returns(true);
// Act
var result = _notificationService.NotifyUser(_user, message);
// Assert
Assert.True(result);
}
We create a test method to verify the NotifyUser() method’s behavior when the input is correct.
First, we set up the required dependencies by creating a message. Then, we use NSubstitute to simulate the IsValidEmail() and SendEmail() methods’ behaviors of the _emailService mock object. We achieve that by calling both methods as we usually would and then calling the NSubstitute’s Return() method. We also pass true both times to indicate that the email is valid and was sent successfully.
Next, we invoke the NotifyUser() method with the test data. Finally, we assert with plain xUnit asserts that the method returns true, indicating that the email was successfully sent. For more on writing tests with xUnit, see our guide on unit testing with xUnit.
Ignoring or Conditionally Matching Arguments
With NSubstitute we can use argument matcher:
[Fact]
public void GivenInputIsNotCorrect_WhenNotifyUserIsInvoked_ThenFalseIsReturned()
{
// Arrange
const string message = "Ignoring or Conditionally Matching Arguments";
_emailService.IsValidEmail(default)
.ReturnsForAnyArgs(false);
_emailService.SendEmail(Arg.Any<string>(), Arg.Is<string>(x => x.Length > 5), message)
.Returns(true);
// Act
var result = _notificationService.NotifyUser(_user, message);
// Assert
Assert.False(result);
}
We create another test method to verify the NotifyUser() method’s behavior when the input is not correct. Then, we set the dependencies and move on to configuring the _emailService methods’ behaviors.
We start with the IsValidEmail() method and want to return false in any case to indicate that every email address will be invalid. For that, we pass default as an argument, this way any string value will match. Then we follow up with the ReturnsForAnyArgs() method, indicating that no matter what argument we have the method should always return false.
Then, we set up the SendEmail() method to always return true. For the first argument, we use Arg.Any<T>() method, where T is a string. This is identical to the default approach we used previously and will match any string. Then we use Arg.Is<string>(x => x.Length > 5) to indicate that we should match any string that is longer than five characters. For the final argument, we just use the message variable.
Finally, we invoke the NotifyUser() method and assert that its result is false, indicating that our code didn’t send the email due to incorrect input.
Verifying Mock Interactions
We can use NSubstitute to check if we call a method or not:
[Fact]
public void GivenInputIsNotCorrect_WhenNotifyUserIsInvoked_ThenFalseIsReturned()
{
// Arrange
const string message = "Ignoring or Conditionally Matching Arguments";
_emailService.IsValidEmail(default)
.ReturnsForAnyArgs(false);
_emailService.SendEmail(Arg.Any<string>(), Arg.Is<string>(x => x.Length > 5), message)
.Returns(true);
// Act
var result = _notificationService.NotifyUser(_user, message);
// Assert
Assert.False(result);
_emailService.Received().IsValidEmail(Arg.Any<string>());
_emailService.DidNotReceive().SendEmail(Arg.Any<string>(), Arg.Is<string>(x => x.Length > 5), message);
}
We update the test method from the previous section by checking whether or not we call the _emailService methods. The way we do this is by calling the Received() method on the _emailService instance and then the method we want to check.
First, we verify that we call the IsValidEmail() method with any string argument. We can also pass a number to the Received() method — Received(1) — to assert that the method in question is called exactly that many times.
We can also verify that we don’t call a method by using the DidNotReceive() method. With it, we verify that the NotifyUser() method doesn’t call the SendEmail() method as is not supposed to when IsValidEmail() returns false.
We can use this to ensure that the expected interactions between the NotificationService and _emailService are met during the unit test scenario.
How Do We Throw Exceptions With NSubstitute?
We can use NSubstitute to specify that a method should throw an Exception. NSubstitute ships the NSubstitute.ExceptionExtensions namespace for exactly this, so we state the exception type up front with Throws<T>():
[Fact]
public void GivenExceptionIsThrown_WhenNotifyUserIsInvoked_ThenExceptionIsPropagated()
{
// Arrange
const string message = "Throwing Exceptions When Mocking With NSubstitute";
_emailService.IsValidEmail(_user.Email)
.Returns(true);
_emailService.SendEmail(_user.Email, "Notification from CodeMaze", message)
.Throws<InvalidEmailException>();
// Act & Assert
Assert.Throws<InvalidEmailException>(() => _notificationService.NotifyUser(_user, message));
}
After adding using NSubstitute.ExceptionExtensions; to the test file, one Throws<InvalidEmailException>() call replaces the older lambda-throw workaround (.Returns(x => { throw new InvalidEmailException(); })), with the exception type stated up front instead of buried in a lambda. We then assert that NotifyUser() lets the exception propagate. This approach works only for non-void methods.
For void methods, where there is no return value to script, NSubstitute’s When() and Do() pair does the same job:
_emailService.When(x => x.SendEmail(_user.Email, "Notification from CodeMaze", message))
.Do(x => { throw new InvalidEmailException(); });
We pass lambda expressions to both methods. To the When() method, we pass the SendEmail() method with the expected arguments, and to the Do() method, the error-throwing lambda. This approach will work for both void and non-void methods.
For Task-returning members — the case most of us hit first in real code — the same extension namespace provides ThrowsAsync<T>():
[Fact]
public async Task GivenExceptionIsThrown_WhenSendEmailAsyncIsAwaited_ThenExceptionIsPropagated()
{
// Arrange
const string message = "Throwing exceptions from asynchronous members";
_emailService.SendEmailAsync(_user.Email, "Notification from CodeMaze", message)
.ThrowsAsync<InvalidEmailException>();
// Act & Assert
await Assert.ThrowsAsync<InvalidEmailException>(() =>
_emailService.SendEmailAsync(_user.Email, "Notification from CodeMaze", message));
}
The setup reads the same as the synchronous version, but the substitute returns a faulted Task instead of throwing inline, which is what an awaiting caller actually observes.
NSubstitute vs Moq: The Syntax Side by Side
The table below maps each NSubstitute call to its Moq equivalent — useful when migrating a test suite or reading a codebase that uses both. For the async side of Moq, see mocking asynchronous methods with Moq, and for a common real-world target, how to mock HttpClient.
| Task | NSubstitute | Moq |
|---|---|---|
| Create a mock | Substitute.For<IService>() | new Mock<IService>() |
| Pass to the class under test | the substitute itself | mock.Object |
| Configure a return | svc.Get(1).Returns(user) | mock.Setup(s => s.Get(1)).Returns(user) |
| Any argument | Arg.Any<string>() | It.IsAny<string>() |
| Verify a call | svc.Received(1).Get(1) | mock.Verify(s => s.Get(1), Times.Once) |
| Throw from a method | svc.Get(1).Throws<MyException>() | mock.Setup(...).Throws<MyException>() |
| License | BSD-3-Clause | Free (BSD) |
Conclusion
In this article, we covered mocking with NSubstitute in .NET and how its syntax keeps test setup short and readable.
NSubstitute simplifies the process of creating mock objects, allowing us to simulate dependencies’ behaviors and return values, and verifying interactions with ease. By effectively isolating and testing individual components, NSubstitute helps us enhance the reliability and overall quality of our applications. However, we must be cautious when using NSubstitute, especially with classes, to avoid unintended execution of real code in our tests.
Tested with .NET 10.0.10, NSubstitute 6.2.0, xUnit 2.9.3.
