Mocking Final Classes and Static Methods
Fast recall
Quick revision
Core answer
Mockito.mockStatic(Type.class) to create a scoped MockedStatic, configure calls inside a try-with-resources block, and close it so the thread-local static behavior does not leak. Some methods and JVM types remain unsupported, and frequent static mocking can signal a hard-to-test design.Interview-ready guide
Interview answer
Modern Mockito can mock final classes and final instance methods through the inline mock maker, which is the default in Mockito 5. Older projects may need different setup, so the project version and active mock maker matter.
Static methods are handled separately. Mockito.mockStatic(Type.class) creates a MockedStatic scope. Inside a try-with-resources block I can stub and verify calls; closing the scope restores normal behavior. This is not a spy on an object, and leaving the scope open can leak behavior into later tests on the same thread.
There are limits: native methods cannot be mocked and some JVM/core types are restricted. Static mocking is useful for a narrow legacy seam, but if many tests need it, an injected clock, ID generator, or gateway usually gives clearer design and simpler tests.
Mocking Final Classes and Static Methods
Direct Answer
Modern Mockito can mock or spy final classes and final instance methods when the inline mock maker is active; Mockito 5 uses inline mocking by default. Static methods are not spied on as part of an object. Use Mockito.mockStatic(Type.class) to create a scoped MockedStatic, configure calls inside a try-with-resources block, and close it so the thread-local static behavior does not leak. Some methods and JVM types remain unsupported, and frequent static mocking can signal a hard-to-test design.
Code Example
final class PaymentGateway {
String process(int amount) { return "real-id"; }
}
@Test
void mocksAFinalClass() {
PaymentGateway gateway = mock(PaymentGateway.class);
when(gateway.process(100)).thenReturn("test-id");
assertEquals("test-id", gateway.process(100));
}
@Test
void scopesAStaticMock() {
try (MockedStatic<ClockUtil> clock = Mockito.mockStatic(ClockUtil.class)) {
clock.when(ClockUtil::today).thenReturn(LocalDate.of(2026, 1, 2));
assertEquals(LocalDate.of(2026, 1, 2), ClockUtil.today());
}
}
Common Mistakes & Pitfalls
- Adding
mockito-inlineautomatically: Mockito 5 uses the inline mock maker by default; check the actual version and configuration first. - Leaking a static mock: always close
MockedStatic, normally with try-with-resources. - Calling static mocking a spy:
mockStaticcreates a scoped static mock, not an object spy. - Using static mocking everywhere: repeated global seams are usually clearer behind an injected collaborator.
- Ignoring limitations: native methods and some JVM-sensitive targets are not ordinary mock seams.
Check your understanding
Answer these without looking back at the article.
- What is the difference between mockito-core and mockito-inline?
- How would you refactor a class that calls System.currentTimeMillis() to make it testable?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.