Fundamentals 5 min read

Don’t Mock Types You Don’t Own: Risks and Better Alternatives

The article explains why mocking external components you don’t control can introduce maintenance problems, such as hidden bugs and upgrade difficulties, and suggests using real implementations, library‑provided fakes, or wrapper classes instead, while offering code examples and practical guidance.

Continuous Delivery 2.0
Continuous Delivery 2.0
Continuous Delivery 2.0
Don’t Mock Types You Don’t Own: Risks and Better Alternatives

When writing automated tests, developers often use mocks to simulate external libraries or components. While mocks let tests focus on the system’s boundaries without invoking real services, they can also lead to a divergence from the actual implementation.

The article highlights several drawbacks of mocking types you do not own. Hard‑coded expectations in mocks may become outdated when a third‑party library is upgraded, requiring time‑consuming test updates. Moreover, mocks can mask bugs introduced by library changes because the test continues to pass even if the real code would fail.

Example of a mock for a salary payment library:

// Mock a salary payment library
@Mock SalaryProcessor mockSalaryProcessor;
@Mock TransactionStrategy mockTransactionStrategy;
...
when(mockSalaryProcessor.addStrategy()).thenReturn(mockTransactionStrategy);
when(mockSalaryProcessor.paySalary()).thenReturn(TransactionStrategy.SUCCESS);
MyPaymentService myPaymentService = new MyPaymentService(mockSalaryProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

Problems arise when the library changes, such as a new method signature for addStrategy() or a new return value for paySalary() . The mock must be updated even though the production code may not need changes, and developers might miss bugs because the mock does not reflect the new behavior.

Therefore, the recommendation is to avoid mocks whenever possible and use the real implementation. If a real implementation cannot be used, prefer a fake provided by the library maintainer, which reduces maintenance overhead.

Example of using a fake implementation:

FakeSalaryProcessor fakeProcessor = new FakeSalaryProcessor(); // Designed for tests
MyPaymentService myPaymentService = new MyPaymentService(fakeProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

When neither a real implementation nor a library‑provided fake is available, create a wrapper class around the external library and mock the wrapper instead. This limits the scope of the mock and isolates third‑party API changes.

@Mock MySalaryProcessor mockMySalaryProcessor; // Wraps the SalaryProcessor library
...
when(mockMySalaryProcessor.sendSalary()).thenReturn(PaymentStatus.SUCCESS);
MyPaymentService myPaymentService = new MyPaymentService(mockMySalaryProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

Testing the wrapper with the actual implementation confines any performance impact to the wrapper’s tests rather than the whole codebase. The article also references the book “Growing Object Oriented Software, Guided by Tests” and provides a link to the original post “Don’t Mock Types You Don’t Own” published on July 16, 2020.

JavaUnit Testingmockingsoftware designTest Doubles
Continuous Delivery 2.0
Written by

Continuous Delivery 2.0

Tech and case studies on organizational management, team management, and engineering efficiency

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.