👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
Unit Testing Anti-Pattern: Mocking Concrete Classes

Unit Testing Anti-Pattern: Mocking Concrete Classes

Authors - Abdul Rahman (Content Writer), Regina Sharon (Graphic Designer)

Testing

7 Articles

Improve

Table of Contents

  1. What we gonna do?
  2. Why we gonna do?
  3. How we gonna do?
  4. Summary

What we gonna do?

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 we gonna do?

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.

How we gonna do?

Consider a class that calculates delivery statistics:

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:

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:

[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:

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:

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.

👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
  • Testing
  • Anti Pattern
  • Unit Testing