In this article, we will delve into the world of mocking with NSubstitute in .NET and explore how it can help us create comprehensive and efficient tests for our projects.

To download the source code for this article, you can visit our GitHub repository.

Let’s get into it!

What Is NSubstitute?

NSubstitute is a popular mocking framework in .NET, similar to Moq, that simplifies the creation of mock objects for unit testing.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

With NSubstitute, we can simulate the behavior of dependencies in our tests. The library provides us with the possibility to define expectations and verify interactions without the need for concrete implementations. This is a powerful tool that allows developers to isolate and test individual components. This leads to more efficient and reliable unit tests and also enhances the overall quality of our applications.

We should be careful when using NSubstitute, as it only works effectively with interfaces or class members that are overridable from the test assembly. When we mock classes with non-virtual or internal virtual members, there’s a risk of unintentionally executing real code in our tests, which may lead to unexpected behavior and it is best to avoid it.

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.

Setting up Our Code

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);
}

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;
        }

        var sentSuccessfully = _emailService.SendEmail(user.Email, "Notification from CodeMaze", message);

        return sentSuccessfully;
    }
}

The NotificationService class is implementing the INotificationService interface and uses an injected IEmailService to send email notifications.

Mocking With NSubstitute

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
    result.Should().BeTrue();
}

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 the result using the FluentAssertions library to ensure that the method returns true, indicating that the email was successfully sent.

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
    result.Should().BeFalse();
}

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
    result.Should().BeFalse();
    _emailService.Received(1).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 exactly once with any string argument.  We can also use the Received() method without passing any arguments which will assert if the method in question is called at least once.

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.

Throwing Exceptions When Mocking With NSubstitute

We can use NSubstitute to specify that a method should throw an Exception. First, we update our NotifyUser() method:

public bool NotifyUser(User user, string message)
{
    if (!_emailService.IsValidEmail(user.Email) ||
        string.IsNullOrWhiteSpace(message))
    {
        return false;
    }

    try
    {
        var sentSuccessfully = _emailService
            .SendEmail(user.Email, "Notification from CodeMaze", message);

        return sentSuccessfully;
    }
    catch
    {
        throw;
    }
}

We wrap the SendEmail() method in a trycatch statement and re-throw if an Exception is caught.

Then, let’s write our test method:

[Fact]
public void GivenExceptionIsThrown_WhenNotifyUserIsInvoked_ThenFalseIsReturned()
{
    // Arrange
    const string message = "Throwing Exceptions When Mocking With NSubstitute";
    _emailService.IsValidEmail(_user.Email)
        .Returns(true);
    _emailService.SendEmail(_user.Email, "Notification from CodeMaze", message)
        .Returns(x => { throw new InvalidEmailException(); });

    // Act
    var act = () => _notificationService.NotifyUser(_user, message);

    // Assert
    act.Should().ThrowExactly<InvalidEmailException>();
}

We set up the behavior of the SendEmail() method using the NSubstitute’s Return() method. For a return value of the method, we use the x => { throw new InvalidEmailException(); } lambda expression which states that a custom InvalidEmailException should be thrown. Then, we call the NotifyUser() method and assert that it throws an InvalidEmailException as well. This approach works only for non-void methods.

But there is an alternative and better solution for this scenario:

[Fact]
public void GivenExceptionIsThrown_WhenNotifyUserIsInvoked_ThenFalseIsReturned()
{
    // Arrange
    const string message = "Throwing Exceptions When Mocking With NSubstitute";
    _emailService.IsValidEmail(_user.Email)
        .Returns(true);
    _emailService.When(x => x.SendEmail(_user.Email, "Notification from CodeMaze", message))
        .Do(x => { throw new InvalidEmailException(); });

    // Act
    var act = () => _notificationService.NotifyUser(_user, message);

    // Assert
    act.Should().ThrowExactly<InvalidEmailException>();
}

We update the same test method by configuring the SendEmail() method using NSubstitute’s When() and Do() methods. We pass lambda expressions to both methods. To the When() method, we pass the SendEmail() method with the expected arguments. For the Do() method we use the same error-throwing lambda from the previous example. This approach will work for both void and non-void methods.

Conclusion

In this article, we delved into the world of mocking with NSubstitute in .NET, exploring its powerful capabilities for creating comprehensive and efficient tests.

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.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!