Mocking Static Blocks en Java


Answers

PowerMock est un autre cadre factice qui étend EasyMock et Mockito. Avec PowerMock, vous pouvez facilement supprimer les comportements indésirables d'une classe, par exemple un initialiseur statique. Dans votre exemple, vous ajoutez simplement les annotations suivantes à votre cas de test JUnit:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

PowerMock n'utilise pas d'agent Java et ne nécessite donc pas de modification des paramètres de démarrage de la machine virtuelle Java. Vous ajoutez simplement le fichier jar et les annotations ci-dessus.

Question

Ma devise pour Java est "juste parce que Java a des blocs statiques, cela ne signifie pas que vous devriez les utiliser." Blagues à part, il y a beaucoup de trucs en Java qui font du test un cauchemar. Deux des plus que je déteste sont les classes anonymes et les blocs statiques. Nous avons beaucoup de code hérité qui utilise des blocs statiques et ceux-ci sont l'un des points gênants dans nos tests unitaires d'écriture. Notre but est de pouvoir écrire des tests unitaires pour les classes qui dépendent de cette initialisation statique avec des changements de code minimes.

Jusqu'à présent, ma suggestion à mes collègues est de déplacer le corps du bloc statique dans une méthode statique privée et l'appeler staticInit . Cette méthode peut ensuite être appelée depuis le bloc statique. Pour les tests unitaires, une autre classe dépendant de cette classe pourrait facilement se moquer de staticInit avec JMockit pour ne rien faire. Voyons cela dans l'exemple.

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

Sera changé en

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

Alors que nous pouvons faire ce qui suit dans un JUnit.

public class DependentClassTest {
  public static class MockClassWithStaticInit {
    public static void staticInit() {
    }
  }

  @BeforeClass
  public static void setUpBeforeClass() {
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
  }
}

Cependant, cette solution comporte également ses propres problèmes. Vous ne pouvez pas exécuter DependentClassTest et ClassWithStaticInitTest sur la même machine ClassWithStaticInitTest car vous souhaitez réellement que le bloc statique s'exécute pour ClassWithStaticInitTest .

Quelle serait votre façon d'accomplir cette tâche? Ou de meilleures solutions non basées sur JMockit qui, selon vous, seraient plus propres?




Vous pouvez écrire votre code de test dans Groovy et simuler facilement la méthode statique en utilisant la métaprogrammation.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b
}

Math.max 1, 2

Si vous ne pouvez pas utiliser Groovy, vous devrez vraiment refactoriser le code (peut-être injecter quelque chose comme un initialiseur).

Sincères amitiés




De temps en temps, je trouve des initiateurs statiques dans les classes dont mon code dépend. Si je ne peux pas refactoriser le code, @SuppressStaticInitializationFor annotation @SuppressStaticInitializationFor pour supprimer l'initialiseur statique:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {

    ClassWithStaticInit tested;

    @Before
    public void setUp() {
        tested = new ClassWithStaticInit();
    }

    @Test
    public void testSuppressStaticInitializer() {
        asserNotNull(tested);
    }

    // more tests...
}

En savoir plus sur la suppression des comportements indésirables

Clause de non-responsabilité: PowerMock est un projet open source développé par deux de mes collègues.




Je ne suis pas très bien informé sur les frameworks Mock, alors corrigez-moi si je me trompe, mais ne pourriez-vous pas avoir deux objets Mock différents pour couvrir les situations que vous mentionnez? Tel que

public static class MockClassWithEmptyStaticInit {
  public static void staticInit() {
  }
}

et

public static class MockClassWithStaticInit {
  public static void staticInit() {
    System.out.println("static initialized.");
  }
}

Ensuite, vous pouvez les utiliser dans vos différents cas de test

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithEmptyStaticInit.class);
}

et

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithStaticInit.class);
}

respectivement.






Links