Mockito: injectez des objets réels dans des champs privés @Autowired

199

J'utilise Mockito @Mocket des @InjectMocksannotations pour injecter des dépendances dans des champs privés qui sont annotés avec Spring @Autowired:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

et

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Maintenant, je voudrais également injecter des objets réels dans des @Autowiredchamps privés (sans setters). Est-ce possible ou le mécanisme est-il limité à l'injection de Mocks uniquement?

user2286693
la source
5
Normalement, lorsque vous vous moquez des choses, cela implique que vous ne vous souciez pas beaucoup de l'objet concret; que vous ne vous souciez vraiment que du comportement de l'objet fictif. Vous souhaitez peut-être faire un test d'intégration à la place? Ou pourriez-vous expliquer pourquoi vous voulez que des objets moqués et concrets vivent ensemble?
Makoto
2
Eh bien, je travaille avec du code hérité et il faudrait beaucoup de déclarations when (...). ThenReturn (...) pour configurer le simulacre juste pour empêcher certains NPE et autres. D'un autre côté, un objet réel pourrait être utilisé en toute sécurité pour cela. Il serait donc très pratique d'avoir une option pour injecter des objets réels avec des simulacres. Même si cela peut être une odeur de code, je considère que c'est raisonnable dans ce cas particulier.
user2286693
N'oubliez pas MockitoAnnotations.initMocks(this);la @Beforeméthode. Je sais que ce n'est pas directement lié à la question d'origine, mais à quiconque viendrait plus tard, cela devrait être ajouté pour rendre cela exécutable.
Cuga
7
@Cuga: si vous utilisez le coureur Mockito pour JUnit, ( @RunWith(MockitoJUnitRunner.class)), vous n'avez pas besoin de la ligneMockitoAnnotations.initMocks(this);
Clint Eastwood
1
Merci - Je n'ai jamais su cela et j'ai toujours spécifié les deux
Cuga

Réponses:

324

Utiliser l' @Spyannotation

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Spy
    private SomeService service = new RealServiceImpl();

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito considérera tous les champs ayant @Mockune @Spyannotation comme des candidats potentiels à injecter dans l'instance annotée avec @InjectMocksannotation. Dans le cas ci-dessus, l' 'RealServiceImpl'instance sera injectée dans la `` démo ''

Pour plus de détails, reportez-vous

Mockito-maison

@Espion

@Moquer

Dev masqué
la source
10
+1: a fonctionné pour moi ... sauf pour les objets String. Mockito se plaint:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk
Merci Cela a fonctionné pour moi aussi :) Pour faire un proxy utiliser mock else pour une implémentation réelle, utilisez spy
Swarit Agarwal
Merci! C'est exactement ce dont j'avais besoin!
nterry
3
Dans mon cas, Mockito n'injecte pas de Spy. Il injecte cependant un Mock. Le champ est privé et sans passeur.
Vituel
8
BTW, ce n'est pas nécessaire new RealServiceImpl(), @Spy private SomeService service;crée un objet réel en utilisant le constructeur par défaut avant de l'espionner de toute façon.
parxier du
20

En plus de la réponse @Dev Blanked, si vous souhaitez utiliser un bean existant créé par Spring, le code peut être modifié pour:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {

    @Inject
    private ApplicationContext ctx;

    @Spy
    private SomeService service;

    @InjectMocks
    private Demo demo;

    @Before
    public void setUp(){
        service = ctx.getBean(SomeService.class);
    }

    /* ... */
}

De cette façon, vous n'avez pas besoin de changer votre code (ajouter un autre constructeur) juste pour que les tests fonctionnent.

Yoaz Menda
la source
2
@Aada Pouvez-vous élaborer?
Yoaz Menda
1
De quelle bibliothèque cela vient-il, je ne peux voir InjectMocks que dans org.mockito
Sameer
1
@sameer import org.mockito.InjectMocks;
Yoaz Menda le
Ajout d'un commentaire pour annuler celui d'Aada. Cela a fonctionné pour moi. Je ne pouvais pas "simplement ne pas utiliser l'injection de champ" comme suggéré par d'autres réponses, donc j'ai dû m'assurer que les Autowiredchamps des dépendances étaient créés correctement.
Scrambo
1
J'ai essayé ceci. mais je reçois une exception de pointeur nul de la méthode de configuration
Akhil Surapuram
2

Mockito n'est pas un framework DI et même les frameworks DI encouragent les injections de constructeur par rapport aux injections de champ.
Vous déclarez donc simplement un constructeur pour définir les dépendances de la classe testée:

@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
   demo = new Demo(serviceMock);
}

Utiliser Mockito spypour le cas général est un terrible conseil. Cela rend la classe de test fragile, pas droite et sujette aux erreurs: qu'est-ce qui est vraiment moqué? Qu'est-ce qui est vraiment testé?
@InjectMockset @Spynuit également à la conception globale car elle encourage les classes gonflées et les responsabilités mixtes dans les classes.
Veuillez lire le spy()javadoc avant de l'utiliser à l'aveugle (l'accent n'est pas le mien):

Crée un espion de l'objet réel. L'espion appelle de vraies méthodes à moins qu'elles ne soient écrasées. Les vrais espions doivent être utilisés avec précaution et occasionnellement , par exemple lorsqu'il s'agit de code hérité.

Comme d'habitude, vous allez lire partial mock warning: La programmation orientée objet aborde la complexité en divisant la complexité en objets SRPy séparés et spécifiques. Comment la simulation partielle s'inscrit-elle dans ce paradigme? Eh bien, ce n'est tout simplement pas le cas ... Une simulation partielle signifie généralement que la complexité a été déplacée vers une méthode différente sur le même objet. Dans la plupart des cas, ce n'est pas ainsi que vous souhaitez concevoir votre application.

Cependant, il y a de rares cas où les simulations partielles sont utiles: traiter du code que vous ne pouvez pas changer facilement (interfaces tierces, refactoring intermédiaire du code hérité, etc.) Cependant, je n'utiliserais pas de simulations partielles pour les nouveaux, pilotés par les tests et bien - code conçu.

davidxxx
la source
Bien que certains de vos points soient corrects, vous n'avez pas répondu à la question. À quoi ressemblerait votre code si vous vouliez utiliser l'implémentation réelle de "SomeService" (quelle est la vraie question ici) - changer "@Mock" en "@Spy" aiderait. Et en parlant d'injection de constructeur - oui! Mais alors utilisez-le et créez un constructeur pour votre classe de test ;-). Et le texte met en garde contre l'utilisation de "moquerie partielle", mais cette question concerne l'utilisation d'espionnage sans se moquer du tout.
dermoritz
@dermoritz Changer "@Mock" en "@Spy" ne signifie pas utiliser l'implémentation réelle. Il utilisera un espion et un espion est une simulation partielle. Et cela peut faire plusieurs choses qui stubing, donc ce n'est pas la vraie implémentation. Vous avez répondu à la question: utiliser un constructeur pour définir le champ et instancier l'implémentation réelle (avec le nouvel idiome () ou un bean autowiring avec un spring runner, voir @MockBean)
davidxxx
2

Au printemps, il existe un utilitaire dédié appelé ReflectionTestUtilsà cet effet. Prenez l'exemple spécifique et injectez dans le champ.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}
Takacsot
la source
J'essayais d'injecter le MockHttpServletRequest de Spring. Le faire avec @Spy a permis à mon sujet de test d'obtenir une simulation différente de celle que j'avais préparée dans ma méthode de test. L'utilisation de ReflectionTestUtils donne le résultat souhaité.
Dominic Cronin
-1

Je sais que c'est une vieille question, mais nous avons été confrontés au même problème en essayant d'injecter des chaînes. Nous avons donc inventé une extension JUnit5 / Mockito qui fait exactement ce que vous voulez: https://github.com/exabrial/mockito-object-injection

ÉDITER:

@InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }
Jonathan S. Fisher
la source