The full picture
- Mock vs fake vs container: mock = scripted interactions (verify a protocol); fake = working lightweight impl (in-memory repo — tests read naturally); container/WireMock = the real technology or a faithful HTTP double (test serialization, timeouts, error bodies). Choose by what the test must prove.
- 'Don't mock what you don't own': mocking
RestTemplate/S3Clientbinds tests to your guess of the library's behavior. Wrap it in your port (DocumentStore), mock the port in service tests, and integration-test the adapter against LocalStack/WireMock. - Context-cache economics:
@MockitoBeanchanges the context key → suites with 30 different mock combos build 30 contexts. Consolidate into shared test configurations (@TestConfigurationwith common fakes) to keep the cache hot. - Signals of over-mocking: verify-heavy tests asserting call sequences of internals; tests that break on refactors that didn't change behavior; mocks returning mocks.
// The port (you own this contract):
public interface PaymentPort { Receipt charge(OrderId id, Money amount); }
// Service test: mock/fake the PORT — no HTTP anywhere
@Test void declinedPaymentReleasesInventory() {
var payments = mock(PaymentPort.class);
when(payments.charge(any(), any())).thenThrow(new PaymentDeclined("insufficient"));
var service = new CheckoutService(new InMemoryInventory(), payments);
assertThatThrownBy(() -> service.checkout(cart))
.isInstanceOf(PaymentDeclined.class);
assertThat(inventory.reserved()).isEmpty(); // the actual behavior
}
// Adapter test: the REAL client against WireMock — timeouts, 5xx, body shapes
@Test void gatewayTimeoutMapsToRetryableException() { … }