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:
- NSubstitute: The mocking framework itself.
- NUnit: The main testing framework.
- NUnit Test Adapter: To integrate with Visual Studio.
- 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: IOrderValidator
, IInventoryProvider
, IOrderCreator
, 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 returnfalse
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 returntrue
andIInventoryProvider
to returnfalse
.
[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
andIInventoryProvider
to returntrue
, andIOrderCreator
to returnfalse
.
[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: