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.
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.
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 try
–catch
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.