web-dev-qa-db-fra.com

Comment fonctionne l'invocation mockito when ()?

Compte tenu de la déclaration suivante de Mockito:

when(mock.method()).thenReturn(someValue);

Comment Mockito crée-t-il un proxy pour quelque chose, étant donné que l'instruction mock.method () passera la valeur de retour à when ()? J'imagine que cela utilise des éléments CGLib, mais j'aimerais savoir comment cela se fait techniquement.

105
marchaos

La réponse courte est que dans votre exemple, le résultat de mock.method() sera une valeur vide appropriée au type; mockito utilise l'indirection via un proxy, l'interception de méthode et une instance partagée de la classe MockingProgress afin de déterminer si un appel d'une méthode sur une maquette sert à remplacer ou à rejouer un comportement de talon existant au lieu de transmettre des informations sur stubbing via la valeur de retour d'une méthode simulée.

Voici une mini-analyse en quelques minutes sur le code mockito. Remarque, ceci est une description très approximative - il y a beaucoup de détails en jeu ici. Je suggère que vous vérifiiez le source sur github vous-même.

Premièrement, lorsque vous simulez une classe en utilisant la méthode mock de la classe Mockito, c'est essentiellement ce qui se passe:

  1. Mockito.mock Délègue à org.mockito.internal.MockitoCore . Mock, en passant les paramètres par défaut en tant que paramètre.
  2. MockitoCore.mock Délègue à org.mockito.internal.util.MockUtil . CreateMock
  3. La classe MockUtil utilise la classe ClassPathLoader pour obtenir une instance de MockMaker à utiliser pour créer la maquette. Par défaut, la classe CgLibMockMaker est utilisée.
  4. CgLibMockMaker utilise une classe empruntée à JMock, ClassImposterizer qui gère la création de la maquette. Les éléments clés de la "magie mockito" utilisée sont le MethodInterceptor utilisé pour créer la maquette: le mockito MethodInterceptorFilter et une chaîne d'occurrences MockHandler, y compris une instance de MockHandlerImpl . L'intercepteur de méthode transmet les invocations à l'instance MockHandlerImpl, qui implémente la logique métier à appliquer lorsqu'une méthode est invoquée sur une maquette (c'est-à-dire, rechercher si une réponse est déjà enregistrée, déterminer si l'invocation représente un nouveau stub, etc.). L'état par défaut est que si un stub n'est pas déjà enregistré pour la méthode appelée, une valeur appropriée au type vide est renvoyée.

Maintenant, regardons le code dans votre exemple:

when(mock.method()).thenReturn(someValue)

Voici l'ordre dans lequel ce code sera exécuté:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

La clé pour comprendre ce qui se passe est ce qui se passe lorsque la méthode sur la maquette est invoquée: des informations sur l’invocation de méthode sont transmises à l’intercepteur de la méthode, qui la délègue à sa chaîne de MockHandler instances, qui délègue finalement à MockHandlerImpl#handle. Pendant MockHandlerImpl#handle, Le gestionnaire factice crée une instance de OngoingStubbingImpl et la transmet à l'instance partagée MockingProgress.

Lorsque la méthode when est invoquée après l'appel de method(), elle est déléguée à MockitoCore.when, Qui appelle stub() méthode de la même classe. Cette méthode décompresse le stubbing en cours de l'instance partagée MockingProgress dans laquelle l'invocation simulée method() a été écrite et le renvoie. Ensuite, la méthode thenReturn est appelée sur l'instance OngoingStubbing.

108
Paul Morie

La réponse courte est que, dans les coulisses, Mockito utilise une sorte de variables globales/stockage pour sauvegarder les informations sur les étapes de construction du stub de méthode (invocation de method (), when (), thenReturn () dans votre exemple), de sorte construire une carte sur ce qui devrait être retourné quand ce qui est appelé sur quel param.

J'ai trouvé cet article très utile: Explication du fonctionnement des frameworks factices basés sur un proxy ( http://blog.rseiler.at/2014/06 /explanation-how-proxy-based-mock.html ). L'auteur a mis en œuvre un cadre de démonstration Mocking, ce qui m'a semblé être une très bonne ressource pour ceux qui souhaitent comprendre le fonctionnement de ces cadres.

À mon avis, c'est une utilisation typique d'Anti-Pattern. Normalement, nous devrions éviter les "effets secondaires" lorsque nous implémentons une méthode, ce qui signifie que la méthode devrait accepter les entrées, effectuer des calculs et renvoyer le résultat - rien d'autre ne change à part cela. Mais Mockito ne fait que violer cette règle à dessein. Ses méthodes stockent un tas d'informations en plus de renvoyer le résultat: Mockito.anyString (), mockInstance.method (), when (), thenReturn, elles ont toutes un "effet secondaire" spécial. C'est aussi pourquoi le framework ressemble à une magie à première vue - nous n'écrivons généralement pas un code comme celui-ci. Cependant, dans le cas du cadre moqueur, cette conception anti-modèle est une excellente conception, car elle conduit à une API très simple.

26
Jacob Wu