
Unit Testing Anti-Pattern: Mocking Concrete Classes
Testing
7 Articles
Table of Contents
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.