skip to content

Many teams follow the guideline 'do not mock types you do not own'. What failure is that guideline trying to prevent, and how do you test code that talks to a third-party library if you cannot mock its classes?

level: middleimportance: should knowfreq 45%

answer

  1. a mock records your guess, not the library's contract
  2. green test, broken production, silent on upgrade
  3. own interface + one adapter
  4. adapter tested for real (WireMock/Testcontainers)
  5. one place holds the assumption

basics

~20 s

A mock of a third-party class encodes your guess about how that library behaves, and Mockito cannot check the guess. The test passes while production fails. Instead wrap the library in a thin interface you own, mock that in unit tests, and cover the wrapper with one real integration test.

solid answer

~50 s

When you mock a class from a library you did not write, the test asserts your belief about that library: that it returns `null` rather than an empty `Optional`, that it throws a checked exception, that a fluent call is even legal. Mockito replays whatever you stubbed, so the test stays green when the belief is wrong or when the library changes semantics on upgrade. You also spread a third-party API shape across your whole test suite. The remedy is an adapter you own: a small interface expressing what your domain needs, with one implementation that calls the library. Unit tests mock that interface, which is safe because you control its contract. The adapter itself gets a narrow integration test against the real thing — a stub HTTP server, a container, or the vendor's own test-mode client. One place holds the assumption, and it is verified.

code

java · 19 lines
java
public interface ExchangeRates {
    Optional<BigDecimal> rate(String from, String to);
}

class VendorExchangeRates implements ExchangeRates {
    private final VendorFxClient client; // third-party, never mocked elsewhere
    // translation lives here only
}

@ExtendWith(MockitoExtension.class)
class PricingServiceTest {
    @Mock ExchangeRates rates;           // safe: the contract is ours

    @Test
    void fallsBackWhenRateMissing() {
        given(rates.rate("EUR", "USD")).willReturn(Optional.empty());
        assertThat(service.quote(order)).isEqualTo(Quote.unavailable());
    }
}

go deeper

for a junior

State the core risk: the mock only repeats what you told it, so a wrong assumption about the library never fails a test. Mention wrapping it in your own interface.

for a middle

Give concrete drift examples (null versus empty Optional, unexpected exception type, non-2xx handling) and describe the adapter plus one integration test per boundary.

for a senior

Add the upgrade-blindness angle and the mechanics (final classes, fluent builders), and say where the contract test lives and what it costs to run.

for a principal

Position it as dependency policy: vendor types stay out of the domain, each adapter carries retries, timeouts and metrics, and the test pyramid is shaped around those few verified seams.

## What the guideline says "Do not mock types you do not own" means: do not create Mockito mocks of classes and interfaces from third-party libraries, JDK internals, or any code whose contract you cannot change or guarantee. Mock the abstractions your own codebase defines. ## The failure it prevents A mock is a recording of an assumption. When you stub a library class you assert three things at once: that the method exists with that signature, that it returns that kind of value in that situation, and that it signals errors that way. Mockito verifies none of it — it only replays what you said. So the test can be perfectly green and completely wrong. Classic versions: stubbing a client to return `null` when the real one returns an empty `Optional`; stubbing a success path that in reality requires `build()` or `execute()` first; stubbing a checked exception the library never throws while the real one throws an unchecked one you do not handle; stubbing an HTTP client to return a 200 body when the real client throws on a non-2xx status. Nothing fails until production traffic arrives. The second failure is upgrade blindness. When the library changes behaviour in a minor release — a new default, a different exception type, a nullability change — your mocks keep telling the old story. The compiler catches signature changes; it cannot catch semantic ones, and mocks silence exactly those. There is a mechanical dimension too: third-party APIs are often final classes, static factories, or fluent builders returning `this`, which makes the mocks awkward or impossible without extra machinery, and the resulting setup bakes the library's internal call chain into your test. ## The remedy: own the abstraction Define a small interface in your own code expressing what your domain needs, not what the library offers. If you use a payment SDK, your interface is `PaymentGateway` with `charge(OrderId, Money): ChargeResult`, not the SDK client with its request and response types. One implementation translates between them. Now the split is clean. Business-logic unit tests mock `PaymentGateway`. That is legitimate because you own the contract: if you decide the method returns a result instead of throwing, you change the interface and the compiler forces every caller and every test to follow. Mocks cannot drift from a contract you control. The adapter becomes the single place holding library assumptions, and it gets a test that exercises the real library: a stub HTTP server such as WireMock or MockWebServer, a Testcontainers instance, the vendor's in-memory or sandbox client, or a contract test. That test is slower, but there is only one per boundary, and it fails when the assumption breaks. ## Reasonable exceptions The rule is a heuristic, not dogma. Mocking a type you do not own is acceptable when it is a stable, well-specified interface with trivial semantics and wrapping would cost more than it returns — for example a `javax.sql.DataSource` in a narrow failure-path test, or a Spring `ApplicationEventPublisher`. The test to apply: could this mock lie to me in a way no other test would catch? If yes, wrap it. ## The payoff beyond testing The wrapper is not ceremony for tests alone. It removes vendor types from your domain vocabulary, gives one place to add retries, timeouts, metrics and error translation, and makes swapping providers a single-class change. Testability is the visible benefit; decoupling is the durable one.

  • Where does the assumption about the third-party library get verified once you stop mocking it?
    In one narrow test of the adapter that uses the real library against a controlled endpoint: a stub HTTP server such as WireMock or MockWebServer, a Testcontainers instance, or the vendor's test-mode client. It is slower than a unit test, but there is one per boundary and it fails on upgrades that change behaviour.
  • Is it ever acceptable to mock a type you do not own?
    Yes, when the type is a small, stable, well-specified interface and the mock cannot plausibly lie in a way nothing else would catch — for example a DataSource used to simulate a connection failure. The decision hinges on whether a wrong assumption could slip through the entire suite; if it could, wrap the type instead.

saying these in an interview costs you the question

  • Believing a green test proves the library behaves as stubbed
  • Spreading a vendor's request and response types across dozens of test files
  • Reaching for the inline mock maker to mock a third-party final class instead of wrapping it
  • Treating the guideline as absolute and wrapping even trivial stable interfaces
  • Adding the adapter but never writing any test that exercises the real library

context