
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.