Mocking static methods, verify vs verifyNoMoreInteractions, mocking final classes (inline mock maker is the default in Mockito 5), mocking parameters, method chains, stubs vs mocks, constructor injection, RETURNS_DEEP_STUBS and why to avoid it, taming randomness, and combining JUnit with Mockito well.
Published September 25, 2026
The "tricky" Mockito questions test whether you know when a mocking trick is a design smell. Answer with the mechanism, then say what you'd prefer. Static mocking, deep stubs and randomness are usually better solved by injecting a dependency.
Short answer: Use Mockito.mockStatic(Type.class) in a try-with-resources block. The mock applies only to the current thread, and only inside the block. Since Mockito 5, the inline mock maker is the default, so no extra mockito-inline dependency is needed (on Mockito 3.4–4.x it was).
@Test
void usesFixedUuid() {
UUID fixed = UUID.fromString("00000000-0000-0000-0000-000000000001");
try (MockedStatic<UUID> uuid = mockStatic(UUID.class)) {
uuid.when(UUID::randomUUID).thenReturn(fixed);
assertEquals(fixed.toString(), orderIds.next());
} // the real static behaviour is restored here
}
Key points to cover:
IdGenerator, Clock). Static mocking is a tool for legacy code you can't change yet.verify() and verifyNoMoreInteractions()?Short answer: verify(mock).m() asserts that a specific interaction happened. verifyNoMoreInteractions(mock) asserts that there were no interactions left unverified on that mock. Every call must have been verified already. (verifyNoInteractions(mock) asserts that the mock was never touched at all.)
Key points to cover:
verifyNoMoreInteractions sparingly. It makes tests brittle, because harmless new calls (a new log or metrics call on the mock) break unrelated tests. Reserve it for cases where "nothing else happens" is the requirement, for example "no further payment calls after a decline".Short answer: Mockito 2.1 introduced the inline mock maker (opt-in, through mock-maker-inline or the mockito-inline artifact), which uses a Java agent and instrumentation to mock final classes and methods. Since Mockito 5, the inline mock maker is the default, so mocking finals just works. Mockito 1.x couldn't do it at all: it subclassed via CGLIB, and final types can't be subclassed. PowerMock was the workaround back then, and it's effectively dead now.
Key points to cover:
Short answer: Create the mock yourself, and pass it in as the argument. No injection magic is needed. Stub it, call the method, then verify.
@Test
void notifiesEveryListener() {
OrderListener a = mock(OrderListener.class), b = mock(OrderListener.class);
publisher.publish(order, List.of(a, b));
verify(a).onOrder(order);
verify(b).onOrder(order);
}
Key points to cover:
foo.bar().baz()?Short answer: Stub each link, returning a mock for the intermediate objects. Or use RETURNS_DEEP_STUBS (see Q8). But mocking long chains is a strong sign of a Law of Demeter violation: the code under test knows too much about object internals.
when(customer.getAccount()).thenReturn(account);
when(account.getTier()).thenReturn(Tier.GOLD);
// better design: customer.tier(), or pass the Tier in directly
Key points to cover:
RestClient, or JPA Criteria) are the one common exception, and there it's usually better to test against a real HTTP stub (WireMock) or database than to mock the chain.Short answer: Both are test doubles:
Other doubles: fakes (working lightweight implementations, such as an in-memory repository), spies (real objects that record calls) and dummies (placeholders).
Key points to cover:
mock() object can act as either. The distinction is how you use it: when (stubbing) vs verify (mocking).Short answer: Create the mocks, and pass them to the constructor directly. That's the simplest and most explicit option, and the compiler flags missing dependencies. @InjectMocks also picks the largest constructor, but it fails more quietly.
@ExtendWith(MockitoExtension.class)
class CheckoutServiceTest {
@Mock PaymentGateway gateway;
@Mock OrderRepository orders;
CheckoutService checkout;
@BeforeEach void setUp() { checkout = new CheckoutService(gateway, orders, Clock.fixed(NOW, UTC)); }
}
RETURNS_DEEP_STUBS, and when would you use it?Short answer: mock(Type.class, RETURNS_DEEP_STUBS) makes every method in a chain automatically return another mock, so when(a.b().c().d()).thenReturn(x) works without stubbing each level.
When: rarely. It's for quickly wrapping fluent APIs you don't control in legacy tests.
Why to avoid it: it hides design problems (deep coupling), produces confusing failures, and doesn't work with generic or final return types in some cases. Mockito's own documentation calls it a code smell. Prefer small interfaces, or real in-memory implementations.
Math.random()?Short answer: Make the randomness a dependency. Inject a RandomGenerator (Java 17's interface), a Random with a fixed seed, or your own IdGenerator/Dice interface. Then tests pass a deterministic implementation, or a mock.
class CouponService {
private final RandomGenerator random;
CouponService(RandomGenerator random) { this.random = random; }
String code() { return "SAVE" + random.nextInt(1000, 10000); }
}
// production: new CouponService(new SecureRandom())
// test: new CouponService(new Random(42)) → a predictable sequence
// or mock: when(random.nextInt(1000, 10000)).thenReturn(4321)
Key points to cover:
Clock), UUIDs and environment variables. Anything non-deterministic belongs behind an injected seam.Short answer: JUnit 5 provides the structure (lifecycle, parameterized tests, assertions, nested scenarios), and Mockito provides the isolation (stubbing collaborators, verifying the important interactions). A comprehensive test class covers:
thenThrow);never());@ExtendWith(MockitoExtension.class)
class RefundServiceTest {
@Mock PaymentGateway gateway;
@Mock RefundRepository refunds;
@InjectMocks RefundService service;
@Nested class WhenPaymentWasCaptured {
@Test void refundsAndRecords() {
when(gateway.refund("ch_1", MONEY_100)).thenReturn(RefundResult.ok("re_1"));
service.refund(capturedPayment("ch_1", MONEY_100));
verify(refunds).save(argThat(r -> r.gatewayRef().equals("re_1")));
}
@Test void recordsFailureWithoutCrashing() {
when(gateway.refund(any(), any())).thenThrow(new GatewayTimeoutException());
assertThrows(RefundPendingException.class, () -> service.refund(capturedPayment("ch_1", MONEY_100)));
verify(refunds).save(argThat(r -> r.status() == RefundStatus.PENDING_RETRY));
}
}
@ParameterizedTest @ValueSource(strings = { "0", "-1" })
void rejectsNonPositiveAmounts(String amount) {
assertThrows(IllegalArgumentException.class, () -> service.refund(capturedPayment("ch_1", new BigDecimal(amount))));
verifyNoInteractions(gateway);
}
}
Key points to cover:
Q: What is BDDMockito?
A: An alias API matching Given–When–Then style: given(repo.findById(1L)).willReturn(Optional.of(o)) and then(mailer).should().send(any()). Same behaviour, different vocabulary.
Q: How do you test code that runs asynchronously with mocks?
A: Use verify(mock, timeout(1000)).method(), or Awaitility for state checks. Better still, inject an executor that runs tasks synchronously in unit tests.
Q: Why can over-mocking make a test suite worthless? A: When every collaborator is mocked, and every interaction verified, tests mirror the implementation. They break on every refactor, and still pass when the real collaborators behave differently. Mock only at the boundaries, and test behaviour.
Q: How do you mock a generic type without unchecked warnings?
A: Use @Mock fields (Mockito infers the generics), or mock(Repository.class) with @SuppressWarnings("unchecked") on a narrowly scoped variable.