When I started practicing TDD writing unit tests became my everyday routine. Over my professional career, I've picked up a few techniques that help write tests more cleanly and verify all of the code dependencies. I use XUnit test framework for writing tests and Moq Nuget package to mock code dependencies. I've noticed that most developers are not familiar with good mock verification techniques, which I will cover in this post.
In this post, I will not cover the benefits of unit testing like better code quality, executable documentation, or ease of executing complex business scenarios swiftly. I find unit testing in most cases beneficial and even mandatory.
Sample Code
To start unit testing some code is needed. I've created a simple class with a single method. Class's method contains logic to create a new order.
public class OrderService
{
private readonly IItemRepository _itemRepository;
private readonly IOrderRepository _orderRepository;
public OrderService(
IItemRepository itemRepository,
IOrderRepository orderRepository)
{
_itemRepository = itemRepository;
_orderRepository = orderRepository;
}
public async Task CreateAsync(int itemId, int itemQuantity)
{
var item = await _itemRepository.GetAsync(itemId);
if (item.Stock < itemQuantity)
throw new Exception($"Item id=[{itemId}] has not enough stock for order.");
Order order = new()
{
ItemId = item.Id,
Quantity = itemQuantity
};
await _orderRepository.CreateAsync(order);
}
}
Let's assume that this retrieves data from a database. Those dependencies are abstracted in repositories. Abstracted dependencies are mandatory for mocking in unit testing.
Testing the code
I like to start with a happy path unit test. But first, let's begin with some test boilerplate code.
public class OrderServiceTests
{
private readonly OrderService _sut;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests()
{
_itemRepositoryMock = new Mock<IItemRepository>();
_orderRepositoryMock = new Mock<IOrderRepository>();
_sut = new OrderService(
_itemRepositoryMock.Object,
_orderRepositoryMock.Object);
}
[Fact]
public void CreateAsync_ShouldCreateNewOrder()
{
Assert.False(true);
}
}
If the code is building successfully and we are getting a failed test result we are all set. The next step is to implement the happy path unit test.
[Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
const int itemId = 1;
const int quantity = 2;
const int existingItemStock = 3;
_itemRepositoryMock.Setup(m => m.GetAsync(It.Is<int>(i => i == itemId)))
.ReturnsAsync(() => new Item
{
Id = itemId,
Stock = existingItemStock
});
await _sut.CreateAsync(itemId, quantity);
_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
}
Let's break down this test. I am using AAA(Arrange, Act, Assert) pattern for all my unit tests. This is an industry-standard and is a clean way to structure tests.
- In the "arrange" part we specify what parameters are being used and set up the mock's method result after call.
- In the "act" part we call the method that is being tested.
- In the "assert" part mock calls are verified that has been set up. I am not using Asserts because my method doesn't return a value.
Although this test passed, it has a problem. I didn't verify if await _orderRepository.CreateAsync(order);
. And I didn't setup _orderRepositry
mock. This is caused by Moq default mock behavior. When you create a mock the behavior is set to Default. How this is performed can be viewed in Moq's source code:
/// <summary>
/// Initializes an instance of the mock with <see cref="MockBehavior.Default"/> behavior.
/// </summary>
/// <example>
/// <code>
/// var mock = new Mock<IFormatProvider>;();
/// </code>
/// </example>
public Mock(): this(MockBehavior.Default)
{
}
MockBehavior
is an enum that specifies your created mocks behavior. Available values and behaviors are:
- Strict: an exception is thrown whenever a method or property is invoked without matching configuration.
- Loose: Moq accepts all invocations and attempts to create a valid return value.
- Default: same as Loose.
By default Moq allows to create unit tests without forcing declaring every expected call. This can speed up test development, but I believe this is a bad practice. Developers must own responsibility for their code and every change they make to the code. This can be ensured by setting MockBehavior
strict. This will require to set mock invocations.
public OrderServiceTests()
{
_itemRepositoryMock = new Mock<IItemRepository>(MockBehavior.Strict);
_orderRepositoryMock = new Mock<IOrderRepository>(MockBehavior.Strict);
_sut = new OrderService(_itemRepositoryMock.Object, _orderRepositoryMock.Object);
}
If mock invocation isn't set up exception is trhowned with following message:
Moq.MockException
IOrderRepository.CreateAsync(Order) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
....
To fix the test we need to add the setup for IOrderRepository
.
[Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
const int itemId = 1;
const int quantity = 2;
const int existingItemStock = 3;
_itemRepositoryMock.Setup(m => m.GetAsync(It.Is<int>(i => i == itemId)))
.ReturnsAsync(() => new Item
{
Id = itemId,
Stock = existingItemStock
});
_orderRepositoryMock.Setup(m => m.CreateAsync(It.IsAny<Order>())).Returns(Task.CompletedTask);
await _sut.CreateAsync(itemId, quantity);
_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
_orderRepositoryMock.Verify(m => m.CreateAsync(It.IsAny<Order>()));
}
After these fixes you can see that we are introducing more verification code. If you have multiple setups all the verifications must be performed. If your code is more complex and has multiple method calls this introduces complexity and trivial code which can be avoided. This can be achieved with MockRepository
class which is helps mock instance creation and management. This class helps to create and verify mocks in one place.
public class OrderServiceTests
{
private readonly OrderService _sut;
private readonly MockRepository _mockRepository;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_itemRepositoryMock = _mockRepository.Create<IItemRepository>();
_orderRepositoryMock = _mockRepository.Create<IOrderRepository>();
_sut = new OrderService(_itemRepositoryMock.Object, _orderRepositoryMock.Object);
}
Using MockRepository we set same mock behavior and we can verify all calls using VerifyAll()
method;
[Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
//test code
//_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
//_orderRepositoryMock.Verify(m => m.CreateAsync(It.IsAny<Order>()));
_mockRepository.VerifyAll();
}
To take a step further we can use Dispose()
method to verify mocks. Dispose()
using XUnit runs after each test, so we can verify all mocks that have been setup and avoid _mockRepository.VerifyAll()
code line in each test method.
public class OrderServiceTests : IDisposable
{
private readonly OrderService _sut;
private readonly MockRepository _mockRepository;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests(){...}
[Fact]
public async Task CreateAsync_ShouldCreateNewOrder(){...}
public void Dispose()
{
_mockRepository.VerifyAll();
}
}
This way we have a neat way to verify mocks and keep the test code cleaner.
Top comments (1)
What if:
How can I verify this line creatorClass.CreateAsync(order);