Stubbing inacabado detectado en Mockito

151

Recibo la siguiente excepción mientras ejecuto las pruebas. Estoy usando Mockito para burlarse. Las sugerencias mencionadas por la biblioteca Mockito no están ayudando.

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
    -> at com.a.b.DomainTestFactory.myTest(DomainTestFactory.java:355)

    E.g. thenReturn() may be missing.
    Examples of correct stubbing:
        when(mock.isOk()).thenReturn(true);
        when(mock.isOk()).thenThrow(exception);
        doThrow(exception).when(mock).someVoidMethod();
    Hints:
     1. missing thenReturn()
     2. you are trying to stub a final method, you naughty developer!

        at a.b.DomainTestFactory.myTest(DomainTestFactory.java:276)
        ..........

Código de prueba de DomainTestFactory. Cuando ejecuto la siguiente prueba, veo la excepción.

@Test
public 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);
}

public class SomeModel extends SomeInputModel{
    protected String address;
    protected List<SomeClass> properties;

    public SomeModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    public String getAddress() {
        return this.address;
    }

}

public class SomeInputModel{

    public NetworkInputModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    protected String Name;
    protected List<SomeClass> properties;

    public String getName() {
        return this.Name;
    }

    public void setName(String value) {
        this.Name = value;
    }
}
Rosa real
fuente
Hola Mureinik, he actualizado la publicación con números de línea
Royal Rose

Respuestas:

371

Estás anidando burlándose de la burla. Estás llamando getSomeList(), lo que hace un poco de burla, antes de que hayas terminado de burlarte MyMainModel. A Mockito no le gusta cuando haces esto.

Reemplazar

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

con

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

Para comprender por qué esto causa un problema, necesita saber un poco sobre cómo funciona Mockito y también saber en qué orden se evalúan las expresiones y declaraciones en Java.

Mockito no puede leer su código fuente, por lo que para saber qué le está pidiendo que haga, depende mucho del estado estático. Cuando llama a un método en un objeto simulado, Mockito registra los detalles de la llamada en una lista interna de invocaciones. El whenmétodo lee la última de estas invocaciones de la lista y registra esta invocación en el OngoingStubbingobjeto que devuelve.

La línea

Mockito.when(mainModel.getList()).thenReturn(someModelList);

provoca las siguientes interacciones con Mockito:

  • mainModel.getList()Se llama método simulado ,
  • El método estático whense llama
  • thenReturnSe llama al método en el OngoingStubbingobjeto devuelto por el whenmétodo.

El thenReturnmétodo puede entonces instruir al simulacro que recibe a través del OngoingStubbingmétodo de manejar cualquier llamada adecuado para el getListmétodo de retorno someModelList.

De hecho, como Mockito no puede ver su código, también puede escribir su burla de la siguiente manera:

mainModel.getList();
Mockito.when((List<SomeModel>)null).thenReturn(someModelList);

Este estilo es algo menos claro de leer, especialmente porque en este caso el nulltiene que ser lanzado, pero genera la misma secuencia de interacciones con Mockito y logrará el mismo resultado que la línea anterior.

Sin embargo, la linea

Mockito.when(mainModel.getList()).thenReturn(getSomeList());

provoca las siguientes interacciones con Mockito:

  1. mainModel.getList()Se llama método simulado ,
  2. El método estático whense llama
  3. Se crea una nueva mockde SomeModel(dentro getSomeList()),
  4. model.getName()Se llama método simulado ,

En este punto, Mockito se confunde. Pensó que te estabas burlando mainModel.getList(), pero ahora estás diciéndote que quieres burlarte del model.getName()método. Para Mockito, parece que estás haciendo lo siguiente:

when(mainModel.getList());
// ...
when(model.getName()).thenReturn(...);

Esto parece una tontería Mockitoya que no puede estar seguro de lo que estás haciendo mainModel.getList().

Tenga en cuenta que no llegamos a la thenReturnllamada al método, ya que la JVM necesita evaluar los parámetros de este método antes de poder llamar al método. En este caso, esto significa llamar al getSomeList()método.

En general, es una mala decisión de diseño confiar en el estado estático, como lo hace Mockito, porque puede conducir a casos en los que se viola el Principio de Menos Asombro. Sin embargo, el diseño de Mockito crea una burla clara y expresiva, incluso si a veces causa asombro.

Finalmente, las versiones recientes de Mockito agregan una línea adicional al mensaje de error anterior. Esta línea adicional indica que puede estar en la misma situación que esta pregunta:

3: está tropezando con el comportamiento de otro simulacro antes de la instrucción 'thenReturn' si se completa

Luke Woodward
fuente
¿Hay alguna explicación de este hecho? La solución funciona. Y no entiendo por qué la creación simulada 'in situ' no funciona. Cuando crea simulacro y pasa simulacro creado por referencia a otro simulacro, funciona.
Capacytron
1
Excelente respuesta, amor SO! Me habría llevado mucho tiempo encontrar esto por mí mismo
Dici
44
Gran respuesta Luke! Explicación muy detallada en palabras simples. Gracias.
Tomasz Kalkosiński
1
Increíble. Lo curioso es que cuando hago la llamada al método directo y depuro lentamente, entonces funciona. El atributo de CGLIB $ BOUND obtendrá el valor verdadero, pero de alguna manera lleva un poco de tiempo. Cuando uso la llamada al método directo y me detengo antes del entrenamiento (cuando ...), veo que el valor es primero falso y luego se vuelve verdadero. Cuando es falso y comienza el entrenamiento, se produce esta excepción.
Michael Hegner
¡Me has alegrado el día! ¡Este es el tipo de error que te hace perder mucho tiempo! Al principio pensé que era algo relacionado con Kotlin
Bronx
1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

Para burlarse de los métodos nulos, pruebe a continuación:

//Kotlin Syntax

 Mockito.`when`(voidMethodCall())
           .then {
                Unit //Do Nothing
            }
takharsh
fuente