Skip to content
Latchkey

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.

junit
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 test

Diagnose it: resolve the effective POM first

Maven merges parent POMs, profiles, and settings before it builds anything. The configuration causing your failure is frequently inherited or activated by a profile that is on locally and off in CI.

Terminal
# the fully resolved configuration Maven will actually use
mvn help:effective-pom | head -60

# which profiles are active here vs on your machine?
mvn help:active-profiles

# full error, offline-safe, no colour codes to confuse the log
mvn -B -e -X <goal> 2>&1 | tail -60

Common 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.

java
@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.

java
@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.

java
// 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/@SpyBean rather than placing manual mocks in the context.
  • Stub in @BeforeEach; avoid static fields and @BeforeAll for mutable mock state.
  • Keep default TestExecutionListeners so per-method mock reset runs.

Frequently asked questions

What causes Spring test "@MockBean state leaks between tests"?
There are 3 common causes: context reuse without per-method reset, manual mocks instead of @mockbean, and stubbing in a static/shared field. Spring caches the context across test classes.
How do I fix Spring test "@MockBean state leaks between tests"?
There are 3 fixes depending on which cause you have: use @mockbean, not a manual @bean mock, re-stub per test, not in @beforeall, and do not disable the mockito listener. Work through them in order, since the first is the most common.
What does Spring test "@MockBean state leaks between tests" actually mean?
A test passes alone but fails when run after another that stubbed the same @MockBean, or a verify(...) counts interactions from a previous test.
How do I stop Spring test "@MockBean state leaks between tests" happening again?
Always use @MockBean/@SpyBean rather than placing manual mocks in the context. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card