Deep Analysis and Solutions for Unfinished Stubbing Detection in Mockito

Nov 23, 2025 · Programming · 22 views · 7.8

Keywords: Mockito | Unit Testing | Java Testing

Abstract: This article provides an in-depth analysis of the common UnfinishedStubbingException in the Mockito framework, revealing the root cause through specific code examples. It explains Mockito's static state management mechanism, demonstrates how parameter evaluation order leads to stubbing interruptions, and offers best practices for code refactoring. The article also explores the trade-offs in Mockito's design philosophy, helping developers fundamentally understand and avoid such issues.

Problem Background and Exception Analysis

In unit test development, Mockito's UnfinishedStubbingException is a common pitfall. This exception typically occurs when developers attempt to nest new mocking calls within unfinished stubbing operations. Technically, this issue stems from Mockito's reliance on static state to track the current stubbing context.

Code Example and Problem Reproduction

Consider the following test code snippet:

@Test
public void myTest(){
    MyMainModel mainModel = Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); // Line 355
}

private List<SomeModel> getSomeList() {
    SomeModel model = Mockito.mock(SomeModel.class);
    Mockito.when(model.getName()).thenReturn("SomeName"); // Line 276
    Mockito.when(model.getAddress()).thenReturn("Address");
    return Arrays.asList(model);
}

When executing this test, Mockito throws UnfinishedStubbingException at line 276. The key lies in Java's parameter evaluation mechanism: when calling thenReturn(getSomeList()), the JVM must first evaluate the getSomeList() parameter, which initiates new mocking operations before the mainModel.getList() stubbing is complete.

Mockito Working Mechanism Analysis

Mockito manages the stubbing process by maintaining an internal list of invocation records. When a method on a mock object is called, the framework records the call details. The when method reads the most recent call from this list and returns an OngoingStubbing object. Subsequently, calling the thenReturn method completes the stubbing configuration.

Normal stubbing flow:

// 1. Call mock method
mainModel.getList();
// 2. Initiate stubbing
Mockito.when(null);
// 3. Complete configuration
thenReturn(someModelList);

Problem flow analysis:

  1. Call mainModel.getList() - Mockito records this call
  2. Call Mockito.when() - Creates OngoingStubbing object
  3. Evaluate getSomeList() parameter - New mocking operations begin
  4. Call model.getName() - Mockito confuses stubbing context

Solutions and Best Practices

Refactor test code to separate mocking operations:

@Test
public void myTest(){
    MyMainModel mainModel = Mockito.mock(MyMainModel.class);
    List<SomeModel> someModelList = getSomeList();
    Mockito.when(mainModel.getList()).thenReturn(someModelList);
}

This refactoring ensures:

Design Philosophy Discussion

Mockito's choice of static state-based design, while potentially violating the "Principle of Least Astonishment" in some scenarios, provides test code with conciseness and expressiveness. Developers need to understand the logic behind this design decision to better leverage the framework's advantages.

Advanced Tips

Modern Mockito versions include more explicit hints in error messages: "you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed." This directly addresses the core issue of nested mocking. Developers are advised to:

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.