3 votes

Test de l'initialisation paresseuse par j.u.f.Supplier avec Mockito

J'ai une classe Sut avec une initialisation paresseuse mise en œuvre à l'aide de java.util.function.Supplier . En fait, il est plus compliqué que le code ci-dessous, mais c'est la forme la plus simple que Mockito ne peut pas tester. Le test ci-dessous génère une erreur Wanted but not invoked ... However, there were other interactions with this mock . Pourquoi Mockito ne compte-t-il pas l'invocation de create ? Le flux de code entre en fait dans create() J'ai vérifié avec le débogueur.

import java.util.function.Supplier;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class TestTimes {

    @Test
    public void testCreateOnlyOnce() {
        Sut sut = spy(new Sut());
        sut.getData();
        sut.getData();
        sut.getData();
        verify(sut, times(1)).create();
    }

    private static class Sut {
        Supplier<Object> data = this::create;

        void getData() {
            data.get();
        }

        Object create() {
            return new Object();
        }
    }
}

3voto

Gabriel Robaina Points 657

Tout d'abord, merci pour cette question bien rédigée.

J'ai testé votre code moi-même et j'ai vu l'erreur que vous avez mentionnée. Cependant, j'ai un peu modifié votre code pendant le débogage... Jetez-y un coup d'œil :

    @Test
    public void testCreateOnlyOnce() {
        Sut sut = spy(new Sut());
        sut.getData();
        sut.getData();
        sut.getData();
        verify(sut, times(1)).create();
    }

    private static class Sut {

        private Supplier<Object> data;

        // Added de data object initialization on the constructor to help debugging.
        public Sut() {
            this.data = this::create;
        }

        void getData() {
            data.get();
        }

        Object create() {
            return new Object();
        }
    }

Ce que j'ai découvert lors du débogage :

  1. Les Sut est appelé correctement à l'intérieur de la classe spy(new Sut()) mais la clause create() n'est pas appelée à cet endroit.
  2. A chaque fois sut.getData() est appelé, le create() est également appelée. Ce qui m'a fait conclure, enfin que :

Dans le constructeur, tout ce que this::create disait à java qu'à chaque fois qu'il a besoin de récupérer le fichier Object du fournisseur, que Object sera récupéré dans la base de données create() méthode. De plus, la create() appelée par le fournisseur provient d'une instance de classe différente de celle que Mockito espionne.

Cela explique pourquoi vous ne pouvez pas le suivre avec verify.

EDIT : D'après mes recherches, c'est en fait le comportement souhaité par le fournisseur. Il crée juste une interface qui a un get() qui appelle la méthode noArgs que vous avez déclarée dans la référence de la méthode. Jetez un coup d'œil à la méthode este sur "Instantiate Supplier Using Method Reference".

3voto

sfiss Points 1114

Je voudrais ajouter quelques mots à l'excellente réponse de Gabriel Pimentas. La raison pour laquelle cela fonctionne ainsi est que mockito crée des copies superficielles de l'espion new Sut() et votre Supplier fait référence à la méthode lambda compilée à l'intérieur de l'original Sut et non l'instance de l'espion.

Voir aussi cette question y la documentation de mockito .

Lorsque vous déboguez votre code, vous pouvez voir comment cela fonctionne :

Sut sut = spy(new Sut()); est maintenant une sous-classe de Sut comme l'instance TestTimes$Sut$MockitoMock$1381634547@5b202a3a . Maintenant, tous les champs de l'original new Sut() sont copiés superficiellement, y compris le Supplier<Object> data . En examinant ce champ à l'intérieur de l'espion, nous pouvons voir qu'il s'agit d'une instance de TestTimes$Sut$$Lambda$1/510109769@1ecee32c c'est-à-dire qu'il pointe vers un lambda à l'intérieur de l'original Sut . Lorsque nous plaçons un point d'arrêt à l'intérieur de la méthode de création, nous pouvons observer que this pointe vers TestTimes$Sut@232a7d73 c'est-à-dire l'original Sut et non l'instance espionnée.

EDIT : Même si ce MCVE ne ressemble probablement pas à votre code réel, les solutions suivantes me viennent à l'esprit :

  • Injecter le fournisseur dans votre Sut (soit pendant la construction, soit en tant que paramètre de l getData .
  • Créez le fournisseur paresseusement dans votre getData (de façon à ce qu'elle pointe vers la mockito-instance)
  • N'utilisez pas de fournisseur, mais appelez simplement create directement si le fournisseur n'est pas transmis de l'extérieur

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X