How to Set Up Mocks in Unit Tests to Return Default Objects

ant_f_dev

Anthony Fung

Posted on August 16, 2023

How to Set Up Mocks in Unit Tests to Return Default Objects

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; }
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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.


Thanks for reading!

This article is from my newsletter. If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox (once per week), plus bonus developer tips too!

💖 💪 🙅 🚩
ant_f_dev
Anthony Fung

Posted on August 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related