Course contentsShow
← C#
Lesson 1642 of 1,90815. Software Quality and TestingFree lesson

The Pitfalls of Over-Mocking

Avoid brittle tests caused by over-specifying interaction details.

In this lesson, you will learn why testing internal implementation details instead of public outcomes leads to brittle, high-maintenance test suites.

The Illusion of Control

When we use Moq or NSubstitute to verify every single interaction—every method call, every property getter, and every specific sequence of operations—we are "over-mocking." While it feels like we are writing thorough tests, we are actually creating a "change-detector" suite. These tests fail not because your code is broken, but because you refactored an internal helper method or changed an implementation detail that didn't affect the end result.

The "White Box" Trap

Consider a Service that calculates a discount. A well-written test verifies that the CalculateDiscount method returns the correct value. An over-mocked test, however, mandates exactly how that service interacts with its dependencies:

// Over-mocked: Fails if you rename internal methods or reorder logic
mockRepo.Verify(r => r.GetBaseRate(), Times.Once);
mockLogger.Verify(l => l.LogInfo("Calculating"), Times.Once);
mockValidator.Verify(v => v.Validate(), Times.Once);
// ... plus 5 more verification lines

If you decide to cache the BaseRate internally or change the logging message, your tests turn red. The business logic is still correct, but your test suite has become a liability. You end up spending more time updating tests to match your code changes than actually writing the code itself.

Shift to State Verification

To avoid this, focus on State Verification over Interaction Verification. Treat your dependencies as black boxes: provide them with inputs (using Stubs) and verify the final state or return value of your system under test.

Ask yourself: "If I deleted this verification line, would a functional bug be able to slip through?" If the answer is "no," you are likely over-mocking. Reserve Verify for critical side effects, such as ensuring a ProcessPayment method is actually called, rather than ensuring every internal configuration step occurred in a specific order.

Key Takeaway: Test the behavior of your system by its outcomes, not by documenting the private steps it takes to get there.