web-dev-qa-db-fra.com

Stubbing inachevé détecté à Mockito

Je reçois l'exception suivante pendant l'exécution des tests. J'utilise Mockito pour me moquer. Les indications mentionnées par la bibliothèque Mockito n’aident en rien.

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 je lance 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;
    }
}
121
Royal Rose

Vous vous nidifiez en vous moquant de l'intérieur. Vous appelez getSomeList(), ce qui vous moque un peu, avant que vous ayez fini de vous moquer de MyMainModel. Mockito n'aime pas quand tu fais ç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 un problème, vous devez connaître un peu le fonctionnement de Mockito et 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 dépend beaucoup de 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 méthode when lit le dernier de ces invocations dans la liste et enregistre cet appel dans l'objet OngoingStubbing qu'elle renvoie.

La ligne

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

provoque les interactions suivantes avec Mockito:

  • La méthode fictive mainModel.getList() est appelée,
  • La méthode statique when est appelée,
  • La méthode thenReturn est appelée sur l'objet OngoingStubbing renvoyé par la méthode when.

La méthode thenReturn peut alors ordonner à la maquette reçue via la méthode OngoingStubbing de gérer tout appel approprié à la méthode getList pour renvoyer someModelList.

En fait, comme Mockito ne peut pas voir votre code, vous pouvez également écrire votre moquerie de la manière suivante:

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

Ce style est un peu moins clair à lire, surtout que dans ce cas, la null doit être convertie, mais génère la même séquence d'interactions avec Mockito et aboutira au 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 fictive mainModel.getList() est appelée,
  2. La méthode statique when est appelée,
  3. Un nouveau mock sur SomeModel est créé (à l'intérieur de getSomeList()),
  4. La méthode fictive model.getName() est appelée,

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

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

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

Notez que l'appel à la méthode thenReturn n'est pas arrivé, car la machine virtuelle Java doit évaluer les paramètres de cette méthode avant de pouvoir l'appeler. Dans ce cas, cela signifie qu’il faut appeler la méthode getSomeList().

Comme dans le cas de Mockito, le choix de l'état statique est généralement une mauvaise décision de conception, car il peut en résulter des cas de violation du principe de moindre étonnement. Cependant, la conception de Mockito permet des moquer clairs et expressifs, même si cela peut parfois vous surprendre.

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 bloquez le comportement d'un autre simulateur à l'intérieur avant l'instruction 'thenReturn' si elle est terminée

297
Luke Woodward