How to Set Up Mocks in Unit Tests to Return Default Objects
Hi Friends.
If you’ve been following this series, you’ll have noticed that we use mocks often in our unit and integration tests. (If you’re a new reader, welcome – you’re invited to browse the archive.) We set up the mocks in the Arrange sections of our tests to either return a value, or to perform some logic required for the test.
In some scenarios, all we need is for a mock to return a default object instance. Complex objects and services can have multiple dependencies, meaning we may need to set up this behaviour on many mocks. While it’s not difficult to do so, it increases the overall amount of test code.
This week, we’ll look at a way to keep setup code to a minimum in these situations.
Setting the Scene
Let’s imagine we’re writing a service for an online shop. Its purpose is to generate reports for sales orders, and we have models to represent a customer; an order; and a report that combines parts of these two data.
public class Customer
{
public int Id { get; set; }
public string? Name { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string[]? ProductsOrdered { get; set; }
}
public class OrderReport
{
public string? CustomerName { get; set; }
public string[]? ProductsOrdered { get; set; }
}
We also have three interfaces: one for a customer data repository, one for an orders repository, and one for a service containing references to the data repositories.
public interface ICustomerRepository
{
Customer GetById(int id);
}
public interface IOrderRepository
{
Order GetById(int id);
}
public interface IRepositories
{
ICustomerRepository CustomerRepository { get; set; }
IOrderRepository OrderRepository { get; set; }
}
Here’s the code for our reports service so far:
public class OrderReportsService
{
private readonly IRepositories _repositories;
public OrderReportsService(IRepositories repositories)
{
_repositories = repositories;
}
public OrderReport GetOrderReport(int orderId)
{
var order = _repositories.OrderRepository.GetById(orderId);
var customer = _repositories.CustomerRepository
.GetById(order.CustomerId);
var report = new OrderReport
{
CustomerName = customer.Name,
ProductsOrdered = order.ProductsOrdered
};
return report;
}
}
Writing a Test
We want a simple test to check that everything’s connected, and that we can get a (non-null) report by calling GetOrderReport
on our service. We’ve created and set up three mocks. The two repository mocks simply return unmodified default objects for the return type; the IRepositories
mock returns the mocked repositories.
[Test]
public void GetOrderReportReturnsOrderReport()
{
// Arrange
var customerRepository = Mock.Of<ICustomerRepository>(cr =>
cr.GetById(It.IsAny<int>()) == new Customer());
var orderRepository = Mock.Of<IOrderRepository>(or =>
or.GetById(It.IsAny<int>()) == new Order());
var repositories = Mock.Of<IRepositories>(r =>
r.CustomerRepository == customerRepository &&
r.OrderRepository == orderRepository);
var reportsService = new OrderReportsService(repositories);
// Act
var report = reportsService.GetOrderReport(1);
// Assert
Assert.That(report, Is.Not.Null);
}
Tidying Up
By default, mocks created in Moq return empty values (i.e. null
for reference types, as is the case here). However, this behaviour changes when we set the DefaultValue
property to DefaultValue.Mock
. Mocks will instead return a mock of the requested type (if the type can be mocked). We can use this to tidy our test up a bit.
[Test]
public void GetOrderReportReturnsOrderReport()
{
// Arrange
var repositories = new Mock<IRepositories>
{
DefaultValue = DefaultValue.Mock
};
var reportsService = new OrderReportsService(repositories.Object);
// Act
var report = reportsService.GetOrderReport(1);
// Assert
Assert.That(report, Is.Not.Null);
}
Summary
Mocking is common when writing unit and integration tests. By default, Moq returns empty values for properties and methods that haven’t been explicitly set up. If your tests involve configuring multiple mocks to return unmodified object instances, it might be worth considering setting the DefaultValue
property on your mocks to DefaultValue.Mock
; mocks will then return mocks of the requested types. Ultimately, you’ll have less code to write while still maintaining the same functionality.
Bonus Developer Tip
If your work involves SQL databases, there’s a chance you use SQL Server Management Studio. If you don’t need all of its power, consider using Azure Data Studio as a lighter alternative – you can change its theme/colour scheme too.
These tips are exclusively for my Substack subscribers and readers, as a thank-you for being part of this journey. Some may be expanded into fuller articles in future, depending on their depth.
But you get to see them here, first.