Testing channel icon Testing · 7 published articles

Unit Testing Anti-Pattern: Mocking Concrete Classes

In this post I will teach you how to avoid common unit testing anti pattern - mocking concrete classes. All with live working demo.

Unit Testing Anti-Pattern: Mocking Concrete Classes
On this page

What you'll learn

Mocking concrete classes is an approach where unit tests replace parts of a concrete class instead of mocking an interface. While this can sometimes be useful, it often leads to design issues by violating the Single Responsibility Principle .

Why it matters

Mocking concrete classes mixes concerns by combining business logic with dependencies that should be isolated. This makes unit tests more fragile and increases maintenance overhead. A cleaner approach is to separate responsibilities and mock interfaces instead.

Implementation

Consider a class that calculates delivery statistics:

C#


public class DeliveryStatistics
{
    public (double totalWeight, double totalCost) ComputeStatistics(int customerId)
    {
        List<ShipmentRecord> shipments = FetchShipments(customerId);

        double totalWeight = shipments.Sum(x => x.Weight);
        double totalCost = shipments.Sum(x => x.Cost);

        return (totalWeight, totalCost);
    }

    public virtual List<ShipmentRecord> FetchShipments(int customerId)
    {
        // Calls an external dependency to retrieve shipments
    }
}
            

The DeliveryStatistics class calculates shipment costs but also directly calls an unmanaged dependency. If a controller depends on it, testing becomes difficult:

C#


public class CustomerOrdersController
{
    private readonly DeliveryStatistics _statistics;

    public CustomerOrdersController(DeliveryStatistics statistics)
    {
        _statistics = statistics;
    }

    public string GetSummary(int customerId)
    {
        var (totalWeight, totalCost) = _statistics.ComputeStatistics(customerId);
        return $"Total weight: {totalWeight}, Total cost: {totalCost}";
    }
}
            

Mocking this class requires making FetchShipments virtual and partially replacing its behavior:

C#


[Fact]
public void Customer_With_No_Shipments()
{
    // Arrange
    var mock = new Mock<DeliveryStatistics> { CallBase = true };
    mock.Setup(x => x.FetchShipments(1)).Returns(new List<ShipmentRecord>());

    var controller = new CustomerOrdersController(mock.Object);

    // Act
    string result = controller.GetSummary(1);

    // Assert
    Assert.Equal("Total weight: 0, Total cost: 0", result);
}
            

Although this allows partial mocking, it introduces unnecessary complexity and violates the Single Responsibility Principle. A better approach is to separate concerns:

C#


public interface IShipmentProvider
{
    List<ShipmentRecord> FetchShipments(int customerId);
}

public class ShipmentService : IShipmentProvider
{
    public List<ShipmentRecord> FetchShipments(int customerId)
    {
        // Calls external service to retrieve shipment data
    }
}

public class DeliveryStatistics
{
    public (double totalWeight, double totalCost) ComputeStatistics(List<ShipmentRecord> shipments)
    {
        double totalWeight = shipments.Sum(x => x.Weight);
        double totalCost = shipments.Sum(x => x.Cost);

        return (totalWeight, totalCost);
    }
}
            

Now, the controller can depend on the interface instead of the concrete class:

C#


public class CustomerOrdersController
{
    private readonly DeliveryStatistics _statistics;
    private readonly IShipmentProvider _shipmentProvider;

    public CustomerOrdersController(DeliveryStatistics statistics, IShipmentProvider shipmentProvider)
    {
        _statistics = statistics;
        _shipmentProvider = shipmentProvider;
    }

    public string GetSummary(int customerId)
    {
        var shipments = _shipmentProvider.FetchShipments(customerId);
        var (totalWeight, totalCost) = _statistics.ComputeStatistics(shipments);
        return $"Total weight: {totalWeight}, Total cost: {totalCost}";
    }
}
            

With this approach, testing becomes easier since we can mock IShipmentProvider instead of the entire class.

Summary

Mocking concrete classes is an anti-pattern that complicates unit tests and violates the Single Responsibility Principle. Instead, separating responsibilities and using dependency injection with interfaces makes the code more maintainable and easier to test.