web-dev-qa-db-fra.com

PowerMock: maquette de la variable finale statique privée, un exemple concret

quelle est la moquerie minimale absolue qui doit être faite pour réussir ce test?

code:

class PrivateStaticFinal {
    private static final Integer variable = 0;
    public static Integer method() { return variable + 1; }
}

tester:

@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivateStaticFinal.class)
class PrivateStaticFinalTest {
    @Test
    public void testMethod() {
        //TODO PrivateStaticFinal.variable = 100
        assertEquals(PrivateStaticFinal.method(), 101);
    }
}

connexes: Mock variables finales statiques privées dans la classe de test (pas de réponse claire)

22
sam boosalis

Avis de non-responsabilité: Après beaucoup de recherches sur divers sujets, j'ai trouvé une réponse. Cela peut être fait, mais le consensus général est que ce n'est pas très sûr, mais vu comment vous faites cela UNIQUEMENT DANS LES TESTS UNITAIRES, je pense que vous acceptez ces risques :)


La réponse n'est pas Mocking, car la plupart des Mocking ne vous permettent pas de pirater une finale. La réponse est un peu plus "hacky", où vous modifiez en fait le champ privé lorsque Java appelle est core Java.lang.reflect.Field et Java.lang.reflect.Modifier classes (réflexion). En regardant cette réponse J'ai pu reconstituer le reste de votre test, sans avoir besoin de moqueries qui résout votre problème.

Le problème avec cette réponse est que je rencontrais NoSuchFieldException en essayant de modifier le variable. L'aide pour cela réside dans n autre article sur la façon d'accéder à un champ qui était privé et non public.

Explication de la réflexion/manipulation du champ:

Puisque Mocking ne peut pas gérer la finale, ce que nous finissons par faire, c'est de pirater la racine du champ lui-même. Lorsque nous utilisons les manipulations Field (réflexion), nous recherchons la variable spécifique à l'intérieur d'une classe/d'un objet. Une fois Java le trouve, nous obtenons ses "modificateurs", qui indiquent à la variable quelles restrictions/règles il a comme final, static, private, public, etc. Nous trouvons la bonne variable, puis nous indiquons au code qu'elle est accessible ce qui nous permet de changer ces modificateurs. Une fois que nous avons changé "l'accès" à la racine pour nous permettre pour le manipuler, nous désactivons la partie "finale" de celui-ci. Nous pouvons alors changer la valeur et la régler selon nos besoins.

Pour le dire simplement, nous modifions la variable pour nous permettre de changer ses propriétés, en supprimant la propriété de final, puis en changeant la valeur car elle n'est plus final. Pour plus d'informations à ce sujet, consultez la publication d'où vient l'idée .

Donc, étape par étape, nous passons la variable que nous voulons manipuler et ...

// Mark the field as public so we can toy with it
field.setAccessible(true);
// Get the Modifiers for the Fields
Field modifiersField = Field.class.getDeclaredField("modifiers");  
// Allow us to change the modifiers
modifiersField.setAccessible(true);
 // Remove final modifier from field by blanking out the bit that says "FINAL" in the Modifiers
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set new value
field.set(null, newValue); 

Combinez tout cela dans une nouvelle SUPER RÉPONSE que vous obtenez.

@RunWith(PowerMockRunner.class)
@PrepareForTest()
class PrivateStaticFinalTest {
    @Test
    public void testMethod(){
      try {
        setFinalStatic(PrivateStaticFinal.class.getDeclaredField("variable"), Integer.valueOf(100));
      } 
      catch (SecurityException e) {fail();}
      catch (NoSuchFieldException e) {fail();}
      catch (Exception e) {fail();}
      assertEquals(PrivateStaticFinal.method(), Integer.valueOf(101));
    }

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        // remove final modifier from field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
}

Mise à jour La solution ci-dessus ne fonctionnera que pour les constantes qui sont initialisées dans un bloc statique. Lorsque vous déclarez et initialisez la constante en même temps, il peut arriver que le compilateur l'inline, auquel cas toute modification de la valeur d'origine est ignorée.

39
Walls