Testing in isolation is a cornerstone of unit testing. However, real-world applications often contain classes that depend on external services, databases, or APIs. To truly test a unit in isolation, we need to replace these dependencies with “mocks” or “stubs”. That’s where NSubstitute comes in.

Why Does NSubstitute Exist?

As developers, when we write unit tests, we’re not looking to test how our code interacts with external services like databases, file systems, or APIs. Instead, we want to test our code’s logic in isolation. Mocking libraries, like NSubstitute, allow us to replace these external dependencies with controlled versions (mocks) that mimic the behavior of the real ones.

NSubstitute exists to simplify the process of creating these mocks. While there are other .NET mocking libraries available, such as Moq and Rhino Mocks, NSubstitute differentiates itself with its simple and fluent syntax, making it incredibly easy for developers to set up, inspect, and verify mocks.

When to Use NSubstitute?

  1. Isolation: When you want to isolate your unit of work from external dependencies.
  2. Control Over Dependencies: When you need to control the behavior of your dependencies to cover various scenarios, especially edge cases.
  3. Verifying Interactions: If you want to verify that a method or property was called on your dependency.
  4. Replacing Hard-to-Use Dependencies: Some dependencies might be challenging to use in a testing environment, like those that require specific hardware or have side effects. Mocking allows you to simulate their behavior.

Practical Usage: An Order Processing System

To better understand how NSubstitute works, let’s dive into a simple example: an order processing system. This example uses XUnit along with NSubstitute.

namespace XUnitTests;

public interface IInventory
{
    bool IsInStock(string item);
    void ReduceStock(string item);
}

public interface IPaymentGateway
{
    bool ProcessPayment(double amount);
}

public class OrderService
{
    private readonly IInventory _inventory;
    private readonly IPaymentGateway _paymentGateway;

    public OrderService(IInventory inventory, IPaymentGateway paymentGateway)
    {
        _inventory = inventory;
        _paymentGateway = paymentGateway;
    }

    public bool ProcessOrder(string item, double amount)
    {
        if (_inventory.IsInStock(item))
        {
            if (_paymentGateway.ProcessPayment(amount))
            {
                _inventory.ReduceStock(item);
                return true;
            }
        }

        return false;
    }
}

To use NSubstitute we can call the Substitute.For in the constructor to mock the interfaces. As you can see in the ProcessOrder_ItemInStockAndPaymentSuccessful_ReturnsTrue test, we say that when “apple” is passed as a parameter the value true will be returned.

public class OrderServiceTests
{
    private readonly IInventory _inventory;
    private readonly IPaymentGateway _paymentGateway;
    private readonly OrderService _orderService;

    public OrderServiceTests()
    {
        _inventory = Substitute.For<IInventory>();
        _paymentGateway = Substitute.For<IPaymentGateway>();
        _orderService = new OrderService(_inventory, _paymentGateway);
    }

    [Fact]
    public void ProcessOrder_ItemInStockAndPaymentSuccessful_ReturnsTrue()
    {
        // Arrange
        _inventory.IsInStock("apple").Returns(true);
        _paymentGateway.ProcessPayment(10.0).Returns(true);

        // Act
        var result = _orderService.ProcessOrder("apple", 10.0);

        // Assert
        Assert.True(result);
        _inventory.Received().ReduceStock("apple");
    }

    [Fact]
    public void ProcessOrder_ItemNotInStock_ReturnsFalse()
    {
        // Arrange
        _inventory.IsInStock("apple").Returns(false);

        // Act
        var result = _orderService.ProcessOrder("apple", 10.0);

        // Assert
        Assert.False(result);
        _inventory.DidNotReceive().ReduceStock("apple");
    }

    [Fact]
    public void ProcessOrder_PaymentFailed_ReturnsFalse()
    {
        // Arrange
        _inventory.IsInStock("apple").Returns(true);
        _paymentGateway.ProcessPayment(10.0).Returns(false);

        // Act
        var result = _orderService.ProcessOrder("apple", 10.0);

        // Assert
        Assert.False(result);
        _inventory.DidNotReceive().ReduceStock("apple");
    }
}

Conclusion

NSubstitute provides a clean, concise, and developer-friendly way to create mocks for .NET applications. While the above example is basic, it demonstrates the core value proposition of NSubstitute – to test in isolation, control and verify interactions with dependencies. If you’re new to unit testing or looking for an alternative mocking library, give NSubstitute a try!

One response to “Understanding NSubstitute in C#: A Practical Introduction”

Leave a comment

Trending