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.
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:
Mockito.mock
Délègue à org.mockito.internal.MockitoCore
. Mock, en passant les paramètres par défaut en tant que paramètre.MockitoCore.mock
Délègue à org.mockito.internal.util.MockUtil
. CreateMockMockUtil
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.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é:
mock.method()
when(<result of step 1>)
<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
.
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.