web-dev-qa-db-fra.com

Qu'est-ce que j'utilise au lieu de Whitebox dans Mockito 2.2 pour définir les champs?

Lorsque j'utilise Mockito 1.9.x, j'utilise Whiteboxpour définir les valeurs des champs pour "injecter" les mocks. Voir l'exemple ci-dessous:

@Before
public void setUp() {

    eventHandler = new ProcessEventHandler();
    securityService = new SecurityServiceMock();
    registrationService = mock(RegistrationService.class);

    Whitebox.setInternalState(eventHandler, "registrationService", registrationService);
    Whitebox.setInternalState(eventHandler, "securityService", securityService);
}

J'aime vraiment cette approche, mais maintenant que j'ai essayé de passer à Mockito2.2.7 J'ai remarqué (ou plutôt, mon IDE a remarqué et m'a dit plusieurs fois) que Whitebox ne se trouvait plus dans Mockito.

J'ai trouvé une alternative, qui peut fonctionner en remplacement, et c'est org.powermock.reflect.Whitebox, le problème est que j'obtiens une autre dépendance (Powermock), juste pour utiliser Whitebox.

Powermock possède également une classe nommée Whitebox, mais malheureusement, elle ne peut pas être utilisée avec Mockito 2.2.x

Existe-t-il de bonnes alternatives dans Mockito que je peux utiliser pour "injecter" manuellement des champs, maintenant que Whitebox n'est plus disponible?


Solution

J'ai écrit dans un commentaire en réponse au post de @JeffBowman. En bref, j'ai choisi de copier le code de WhiteBox, et de l'utiliser, car il est utilisé dans la plupart des cas de test et la classe n'a pas de dépendances avec d'autres classes. C'était le chemin le plus rapide pour résoudre ce problème.

Note La solution suggérée par @bcody est une meilleure alternative, si vous utilisez spring, elle ne contient aucun code supplémentaire à maintenir. J'ai reçu ces informations trop tard :(

17

Notez que Whitebox était toujours dans le org.mockito.internal paquet. Au-delà de l'incrémentation du numéro de version majeur, la désignation internal est un signe que le package peut être sujet à des changements de rupture.

Si vous voulez faire un point de définir des champs autrement inaccessibles dans votre test, vous pouvez le faire de la même manière que setInternalState, ce qui est juste pour identifier le champ dans la hiérarchie, appelez setAccessible dessus, puis définissez-le. Le code complet est ici sur grepcode. Vous pouvez également examiner un certain nombre de autres façons de définir un état inaccessible dans les tests .

public static void setInternalState(Object target, String field, Object value) {
    Class<?> c = target.getClass();
    try {
        Field f = getFieldFromHierarchy(c, field);  // Checks superclasses.
        f.setAccessible(true);
        f.set(target, value);
    } catch (Exception e) {
        throw new RuntimeException(
            "Unable to set internal state on a private field. [...]", e);
    }
}

Cependant, dans des situations comme celle-ci, mon conseil général est de arrêter de combattre les outils: les quatre niveaux d'encapsulation de Java ( public, protected, package, private) ne sont pas nécessairement suffisamment granulaires pour exprimer le degré de protection que vous essayez d'exprimer, et il est souvent beaucoup plus facile d'ajouter une méthode d'initialisation bien documentée ou un remplacement de constructeur pour remplacer les dépendances lorsque vous êtes essayer de faire de manière réfléchie. Si vous placez vos tests dans le même package Java que la classe qu'il teste, vous pouvez même souvent rendre les champs ou la méthode/constructeur package-privé, ce qui est également une bonne raison de configurer en parallèle dossiers source src et tests (etc) qui représentent les deux moitiés du même Java package.

Bien que certains traitent cette méthode ou constructeur supplémentaire comme une "pollution API", je le vois plutôt comme un codage conforme aux exigences de l'un des consommateurs les plus importants de votre classe - son propre test. Si vous avez besoin d'une interface externe vierge, vous pouvez facilement en définir une séparément afin de pouvoir masquer tous les détails que vous souhaitez. Cependant, vous pouvez trouver comme la possibilité d'injecter n'importe quelle implémentation réelle ou fictive directement dans votre composant désormais plus flexible, auquel cas vous voudrez peut-être examiner les modèles ou les cadres d'injection de dépendance.

7
Jeff Bowman

Si vous utilisez Spring (la bibliothèque Spring-test en particulier), vous pouvez simplement utiliser ReflectionTestUtils.setField au lieu de Whitebox.setInternalState

34
bcody

La manière la plus propre, la plus ordonnée et la plus portable sans réinventer la roue est d'utiliser le FieldUtils d'Apache Commons. https://commons.Apache.org/proper/commons-lang/apidocs/org/Apache/commons/lang3/reflect/FieldUtils.html

La réponse à votre question serait alors

public static void setStaticFieldValue(
        @NonNull final Class<?> clz,
        @NonNull final String fieldName,
        @NonNull final Object value) throws Exception {
    final Field f = FieldUtils.getField(clz, fieldName, true);
    FieldUtils.removeFinalModifier(f);
    f.set(null, value);
}
4
foo

Vous pouvez utiliser FieldSetter dans Mockito2.x

    import org.mockito.internal.util.reflection.FieldSetter;
 FieldSetter.setField(eventHandler,eventHandler.getClass().getDeclaredField("securityService"), securityService);
2
Murik

Ici avec fest-reflect api vous pouvez trouver une API fluide et facile à utiliser pour le support de la réflexion. C'est ce que j'utilise comme alternative au Whiltebox de Mockito.

1
Priyal85