Mastering Unit Testing with NSubstitute: A Comprehensive Guide

In this blog post, I will introduce you to NSubstitute, a friendly mocking framework for .NET that I stumbled upon and found incredibly useful in my projects. We’ll explore how to set it up, how to use it for unit testing, and some of its advanced features. If you’re new to unit testing or looking to enhance your testing skills, this guide is for you.

Setting Up Your Test Environment

To get started with NSubstitute, you’ll need to add a few NuGet packages to your test project:

  1. NSubstitute: The mocking framework itself.
  2. NUnit: The main testing framework.
  3. NUnit Test Adapter: To integrate with Visual Studio.
  4. Microsoft.NET.Test.Sdk: To make everything work smoothly.

Unit Testing with NSubstitute

Let’s dive into a practical example. We’ll unit test an OrderProcessor class, focusing on its Process method. The OrderProcessor depends on four interfaces: IOrderValidatorIInventoryProviderIOrderCreator, and INotificationSender. These interfaces have no implementations; they are just plain interfaces.

Here’s a brief overview of the Process method:

  • Null Check: Throws an ArgumentException if the order is null.
  • Validation: Uses IOrderValidator to validate the order.
  • Inventory Check: Uses IInventoryProvider to check inventory.
  • Order Creation: Uses IOrderCreator to create the order.
  • Notification: Uses INotificationSender to notify the user.
  • Return Value: Returns true if the process is successful, false otherwise.
public class OrderProcessor 
{
    private readonly IOrderValidator orderValidator;
    private readonly IInventoryProvider inventoryProvider;
    private readonly IOrderCreator orderCreator;
    private readonly INotificationSender notificationSender;

    public OrderProcessor(
        IOrderValidator orderValidator, 
        IInventoryProvider inventoryProvider, 
        IOrderCreator orderCreator, 
        INotificationSender notificationSender)
    {
        this.orderValidator = orderValidator;
        this.inventoryProvider = inventoryProvider;
        this.orderCreator = orderCreator;
        this.notificationSender = notificationSender;
    }

    public bool Process(Order order)
    {
        if (order == null) throw new ArgumentException();

        if(!orderValidator.Validate(order)) throw new Exception("Invalid!");

        if(!inventoryProvider.IsAvailable(order)) throw new Exception("Missing Inventory");

        if(!orderCreator.CreateOrder(order)) return false;

        notificationSender.SendNotification(order);
        return true;
    }
}

public interface IOrderValidator
{
    bool Validate(Order order);
}

public interface IInventoryProvider 
{
    bool IsAvailable(Order order);
}

public interface IOrderCreator 
{
    bool CreateOrder(Order order);
}

public interface INotificationSender 
{
    void SendNotification(Order order);
}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
}

Simple Test Case: Null Order

First, let’s handle a simple test case where the order passed is null. We expect an ArgumentException.

[Test]
public void Process_OrderIsNull_ThrowsArgumentException()
{
    var orderProcessor = new OrderProcessor(null, null, null, null);
    Assert.Throws<ArgumentException>(() => orderProcessor.Process(null));
}

Mocking with NSubstitute

Now, let’s use NSubstitute to mock dependencies and test the Process method more thoroughly.

Invalid Order Test:

  • We mock IOrderValidator to return false for any order, indicating it’s invalid.
[Test]
public void Process_InvalidOrder_ThrowsInvalidOrderException()
{
    var validator = Substitute.For<IOrderValidator>();
    validator.Validate(Arg.Any<Order>()).Returns(false);

    var inventoryProvider = Substitute.For<IInventoryProvider>();

    var orderProcessor = new OrderProcessor(validator, inventoryProvider, null, null);
    Assert.Throws<InvalidOrderException>(() => orderProcessor.Process(new Order()));
}

Inventory Check Test:

  • We mock IOrderValidator to return true and IInventoryProvider to return false.
[Test]
public void Process_ItemNotInInventory_ThrowsInventoryException()
{
    var validator = Substitute.For<IOrderValidator>();
    validator.Validate(Arg.Any<Order>()).Returns(true);

    var inventoryProvider = Substitute.For<IInventoryProvider>();
    inventoryProvider.IsAvailable(Arg.Any<Order>()).Returns(false);

    var orderProcessor = new OrderProcessor(validator, inventoryProvider, null, null);
    Assert.Throws<InventoryException>(() => orderProcessor.Process(new Order()));
}

Order Creation Test:

  • We mock IOrderValidator and IInventoryProvider to return true, and IOrderCreator to return false.
[Test]
public void Process_OrderCreationFails_ReturnsFalse()
{
    var validator = Substitute.For<IOrderValidator>();
    validator.Validate(Arg.Any<Order>()).Returns(true);

    var inventoryProvider = Substitute.For<IInventoryProvider>();
    inventoryProvider.IsAvailable(Arg.Any<Order>()).Returns(true);

    var orderCreator = Substitute.For<IOrderCreator>();
    orderCreator.CreateOrder(Arg.Any<Order>()).Returns(false);

    var orderProcessor = new OrderProcessor(validator, inventoryProvider, orderCreator, null);
    var result = orderProcessor.Process(new Order());
    
    Assert.IsFalse(result);
}

Successful Order Test:

  • We mock all dependencies to return true and ensure the process completes successfully.
[Test]
public void Process_SuccessfulOrder_ReturnsTrue()
{
    var validator = Substitute.For<IOrderValidator>();
    validator.Validate(Arg.Any<Order>()).Returns(true);

    var inventoryProvider = Substitute.For<IInventoryProvider>();
    inventoryProvider.IsAvailable(Arg.Any<Order>()).Returns(true);

    var orderCreator = Substitute.For<IOrderCreator>();
    orderCreator.CreateOrder(Arg.Any<Order>()).Returns(true);

    var notificationSender = Substitute.For<INotificationSender>();

    var orderProcessor = new OrderProcessor(validator, inventoryProvider, orderCreator, notificationSender);
    var result = orderProcessor.Process(new Order());
    
    Assert.IsTrue(result);
}

Advanced Features

NSubstitute offers advanced features like creating substitutes for multiple interfaces and mocking delegates. Here’s how you can use them:

Multiple Interfaces:

var validatorAndDisposable = Substitute.For<IOrderValidator, IDisposable>();

Mocking Delegates:

var func = Substitute.For<Func<string>>();
func().Returns("test");
Assert.AreEqual("test", func());

Conclusion

NSubstitute is a powerful and user-friendly mocking framework that simplifies unit testing in .NET. By mocking dependencies, you can isolate and test individual components effectively. If you found this guide helpful, please head over to my YouTube channel, give it a thumbs up, and subscribe to the channel for more insights.

Related Videos from my YouTube Channel: