Stubbing inachevé détecté dans Mockito

151

Je reçois l'exception suivante lors de l'exécution des tests. J'utilise Mockito pour me moquer. Les conseils mentionnés par la bibliothèque Mockito n'aident pas.

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

Code de test de DomainTestFactory. Lorsque j'exécute le test suivant, je vois l'exception.

@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;
    }
}
Rose royale
la source
Salut Mureinik, j'ai mis à jour le message avec les numéros de ligne
Royal Rose

Réponses:

371

Vous vous moquez de vous moquer. Vous appelez getSomeList(), ce qui fait des moqueries, avant d'avoir fini de vous moquer MyMainModel. Mockito n'aime pas ça quand vous faites ça.

Remplacer

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

avec

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

Pour comprendre pourquoi cela pose problème, vous devez en savoir un peu plus sur le fonctionnement de Mockito, et également savoir dans quel ordre les expressions et les instructions sont évaluées en Java.

Mockito ne peut pas lire votre code source, donc pour comprendre ce que vous lui demandez de faire, il repose beaucoup sur l'état statique. Lorsque vous appelez une méthode sur un objet fictif, Mockito enregistre les détails de l'appel dans une liste interne d'appels. La whenméthode lit le dernier de ces appels dans la liste et enregistre cet appel dans l' OngoingStubbingobjet qu'elle renvoie.

La ligne

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

provoque les interactions suivantes avec Mockito:

  • La méthode simulée mainModel.getList()est appelée,
  • La méthode statique whenest appelée,
  • La méthode thenReturnest appelée sur l' OngoingStubbingobjet retourné par la whenméthode.

La thenReturnméthode peut ensuite demander au simulacre qu'elle a reçu via la OngoingStubbingméthode de gérer tout appel approprié à la getListméthode à renvoyer someModelList.

En fait, comme Mockito ne peut pas voir votre code, vous pouvez également écrire votre moquerie comme suit:

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

Ce style est un peu moins clair à lire, d'autant plus que dans ce cas le nulldoit être casté, mais il génère la même séquence d'interactions avec Mockito et obtiendra le même résultat que la ligne ci-dessus.

Cependant, la ligne

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

provoque les interactions suivantes avec Mockito:

  1. La méthode simulée mainModel.getList()est appelée,
  2. La méthode statique whenest appelée,
  3. Un nouveau mockde SomeModelest créé (à l'intérieur getSomeList()),
  4. La méthode simulée model.getName()est appelée,

À ce stade, Mockito est confus. Il pensait que vous vous moquiez mainModel.getList(), mais maintenant vous lui dites que vous voulez vous moquer de la model.getName()méthode. Pour Mockito, il semble que vous faites ce qui suit:

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

Cela semble idiot Mockitocar il ne peut pas être sûr de ce que vous faites mainModel.getList().

Notez que nous ne sommes pas parvenus à l' thenReturnappel de méthode, car la machine virtuelle Java doit évaluer les paramètres de cette méthode avant de pouvoir appeler la méthode. Dans ce cas, cela signifie appeler la getSomeList()méthode.

En général, c'est une mauvaise décision de conception de s'appuyer sur un état statique, comme le fait Mockito, car cela peut conduire à des cas où le principe du moindre étonnement est violé. Cependant, le design de Mockito permet une moquerie claire et expressive, même si cela conduit parfois à l'étonnement.

Enfin, les versions récentes de Mockito ajoutent une ligne supplémentaire au message d'erreur ci-dessus. Cette ligne supplémentaire indique que vous êtes peut-être dans la même situation que cette question:

3: vous stubbing le comportement d'une autre maquette à l'intérieur avant l'instruction 'thenReturn' si elle est terminée

Luke Woodward
la source
Y a-t-il une explication à ce fait? La solution fonctionne. Et je ne comprends pas pourquoi la création simulée «sur place» ne fonctionne pas. Lorsque vous créez une maquette et passez une maquette créée par référence à une autre maquette, cela fonctionne.
Capacytron
1
Excellente réponse, j'adore SO! Il m'aurait fallu des années pour trouver cela par moi
Dici
4
Bonne réponse Luke! Explication très détaillée en mots simples. Je vous remercie.
Tomasz Kalkosiński
1
Impressionnant. Le plus drôle, c'est que lorsque je fais l'appel de méthode directe et que je débogue lentement, cela fonctionne. L'attribut de CGLIB $ BOUND aura la valeur true, mais cela prend un peu de temps. Quand j'utilise l'appel de méthode direct et que j'arrête avant l'entraînement (quand ...), alors je vois que la valeur est d'abord false, et devient plus tard vraie. Lorsqu'il est faux et que l'entraînement démarre, cette exception se produit.
Michael Hegner
Tu as fait ma journée! C'est le genre d'erreur qui vous fait perdre beaucoup de temps! Je pensais que c'était quelque chose lié à kotlin au début
Bronx
1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

Pour vous moquer des méthodes d'annulation, essayez ci-dessous:

//Kotlin Syntax

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