Spring Test "@MockBean state leaks between tests" - Fix in CI
A @MockBean is reset by Spring only when the test framework resets it around each method; if the cached application context is shared and reset is bypassed, stubs and verified interactions from an earlier test bleed into a later one and cause order-dependent failures.
What this error means
A test passes alone but fails when run after another that stubbed the same @MockBean, or a verify(...) counts interactions from a previous test. Failures are non-deterministic and depend on execution order.
org.mockito.exceptions.verification.TooManyActualInvocations:
paymentClient.charge(<any>);
Wanted 1 time:
But was 2 times:
-> at com.example.PaymentTest.charges(PaymentTest.java:41)
-> at com.example.RefundTest.refunds(RefundTest.java:33) // leaked from prior testCommon causes
Context reuse without per-method reset
Spring caches the context across test classes. @MockBean is reset by MockitoTestExecutionListener (BEFORE/AFTER each method) - if that listener is disabled or overridden, stubs persist.
Manual mocks instead of @MockBean
A hand-rolled mock() placed in the context as a @Bean is never reset by Spring, so its state accumulates across the whole suite.
Stubbing in a static/shared field
Stubs stored on a static field, or set in @BeforeAll, survive every test in the class regardless of mock reset.
How to fix it
Use @MockBean, not a manual @Bean mock
Let Spring manage the mock so its MockReset.AFTER semantics clear it around each test method.
@SpringBootTest
class PaymentTest {
@MockBean PaymentClient paymentClient; // auto-reset per method
}Re-stub per test, not in @BeforeAll
Set up stubbing in @BeforeEach so each method starts from a clean mock.
@BeforeEach
void stub() {
when(paymentClient.charge(any())).thenReturn(Receipt.ok());
}Do not disable the Mockito listener
Ensure default test execution listeners remain active; merging custom listeners must keep MockitoTestExecutionListener.
// If you set listeners explicitly, MERGE rather than replace
@TestExecutionListeners(
listeners = MyListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)How to prevent it
- Always use
@MockBean/@SpyBeanrather than placing manual mocks in the context. - Stub in
@BeforeEach; avoid static fields and@BeforeAllfor mutable mock state. - Keep default
TestExecutionListenersso per-method mock reset runs.