Utilisation avancée de JMockit

1. Introduction

Dans cet article, nous allons au-delà des notions de base de JMockit et examinons certains scénarios avancés, tels que:

  • Fake (ou l’API MockUp )

  • La classe utilitaire Deencapsulation

  • Comment simuler plus d’une interface en utilisant une seule maquette

  • Comment réutiliser les attentes et les vérifications

Si vous souhaitez découvrir les bases de JMockit, consultez les autres articles de cette série. Vous pouvez trouver des liens pertinents au bas de la page.

2. Méthodes privées/classes intérieures Mocking

Se moquer et tester des méthodes privées ou des classes internes n’est souvent pas considéré comme une bonne pratique.

Le raisonnement derrière cela est que si elles sont privées, elles ne devraient pas être testées directement car elles sont les entrailles les plus profondes de la classe, mais parfois cela doit encore être fait, en particulier avec le code hérité.

Avec JMockit, vous avez deux options pour les gérer:

  • L’API MockUp pour modifier l’implémentation réelle (pour la seconde

Cas) ** La classe d’utilitaires Deencapsulation , pour appeler directement n’importe quelle méthode (pour

le premier cas)

Tous les exemples suivants seront effectués pour la classe suivante et nous supposerons qu’ils sont exécutés sur une classe de test avec la même configuration que la première (pour éviter de répéter le code):

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

   //default constructor omitted

    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}

2.1. Faire semblant avec MockUp

L’API Mockup de JMockit prend en charge la création de fausses implémentations ou mock-ups. En règle générale, un mock-up cible quelques méthodes et/ou constructeurs de la classe à falsifier, tout en laissant la plupart des autres méthodes et constructeurs non modifiés. Cela permet une réécriture complète d’une classe, ainsi toute méthode ou constructeur (avec n’importe quel modificateur d’accès) peut être ciblé.

Voyons comment redéfinir privateMethod () à l’aide de l’API de la maquette:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp<AdvancedCollaborator>() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

Dans cet exemple, nous définissons un nouveau MockUp pour la classe AdvancedCollaborator en utilisant l’annotation @ Mock sur une méthode avec une signature correspondante. Après cela, les appels à cette méthode seront délégués à notre simulé.

Nous pouvons également l’utiliser pour mock-up le constructeur d’une classe qui a besoin d’arguments spécifiques ou d’une configuration afin de simplifier les tests:

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp<AdvancedCollaborator>() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

Dans cet exemple, nous pouvons voir que pour les constructeurs moqueurs, vous devez vous moquer de la méthode $ init . Vous pouvez passer un argument supplémentaire de type Invocation, avec lequel vous pouvez accéder aux informations relatives à l’appel de la méthode simulée, y compris l’instance sur laquelle l’appel est effectué.

2.2. Utilisation de la classe Deencapsulation

JMockit inclut une classe d’utilitaire de test: la Deencapsulation . Comme son nom l’indique, il est utilisé pour désencapsuler l’état d’un objet. En l’utilisant, vous pouvez simplifier les tests en accédant à des champs et à des méthodes inaccessibles.

Vous pouvez invoquer une méthode:

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

Vous pouvez également définir des champs:

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

Et obtenir des champs:

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

Et créez de nouvelles instances de classes:

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

Même de nouvelles instances de classes internes:

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

Comme vous pouvez le constater, la classe Deencapsulation est extrêmement utile pour tester des classes étanches. Un exemple pourrait consister à définir les dépendances d’une classe qui utilise les annotations @ Autowired sur des champs privés et que ces derniers ne possèdent pas de paramètres de configuration, ou à tester les classes internes sans avoir à dépendre de l’interface publique de la classe de conteneur.

3. Se moquer de plusieurs interfaces dans une même maquette

Supposons que vous souhaitiez tester une classe - pas encore implémentée - mais vous savez avec certitude qu’elle implémentera plusieurs interfaces.

Généralement, vous ne pourrez pas tester ladite classe avant de l’implémenter, mais avec JMockit, vous avez la possibilité de préparer des tests à l’avance en moquant plusieurs interfaces à l’aide d’un seul objet fictif.

Ceci peut être réalisé en utilisant des génériques et en définissant un type qui étend plusieurs interfaces. Ce type générique peut être défini pour une classe de test complète ou pour une seule méthode de test.

Par exemple, nous allons créer une maquette pour les interfaces List et Comparable de deux manières _: _

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest<MultiMock
  extends List<String> & Comparable<List<String>>> {

    @Mocked
    private MultiMock multiMock;

    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public <M extends List<String> & Comparable<List<String>>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

Comme vous pouvez le voir à la ligne 2, nous pouvons définir un nouveau type de test pour l’ensemble du test en utilisant des génériques sur le nom de la classe. De cette façon, MultiMock sera disponible en tant que type et vous pourrez créer des simulacres pour lui en utilisant toutes les annotations de JMockit.

Dans les lignes 7 à 18, nous pouvons voir un exemple utilisant une maquette d’une multi-classe définie pour l’ensemble de la classe de test.

Si vous avez besoin de la maquette multi-interface pour un seul test, vous pouvez y parvenir en définissant le type générique sur la signature de la méthode et en transmettant une nouvelle maquette de ce nouveau générique comme argument de la méthode de test. Aux lignes 20 à 32, nous pouvons voir un exemple de ce faire pour le même comportement testé que dans le test précédent.

4. Réutilisation des attentes et des vérifications

En fin de compte, lors du test des classes, vous pouvez rencontrer des cas où vous répétez les mêmes Expectations et/ou Verifications plusieurs fois.

Pour faciliter cela, vous pouvez les réutiliser facilement.

Nous allons l’expliquer par un exemple (nous utilisons les classes Model, Collaborator et Performer de notre article de lien:/jmockit-101[JMockit 101]):

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;

    @Mocked
    private Model model;

    @Tested
    private Performer performer;

    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0;
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }

    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls;
        }};
    }

    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls;
        }
    }

    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

Dans cet exemple, vous pouvez voir dans les lignes 15 à 18 que nous préparons une attente pour chaque test afin que model.getInfo () renvoie toujours «foo» et pour collaborator.collaborate () toujours attendre "Foo" comme argument et retourne true . Nous avons mis l’instruction minTimes = 0 afin qu’aucun échec n’apparaisse lorsque vous ne les utilisez pas réellement dans les tests.

Nous avons également créé la méthode verifyTrueCalls (int) pour simplifier les vérifications de la méthode collaborator.receive (boolean) lorsque l’argument transmis est true .

Enfin, vous pouvez également créer de nouveaux types d’attentes et de vérifications spécifiques en étendant simplement l’une des classes Expectations ou Verifications . Ensuite, vous définissez un constructeur si vous devez configurer le comportement et créer une nouvelle instance de ce type dans un test, comme nous le faisons dans les lignes 33 à 43.

5. Conclusion

Avec cet article de la série JMockit, nous avons abordé plusieurs sujets avancés qui vous aideront certainement à vous moquer et à tester chaque jour.

Nous pouvons faire plus d’articles sur JMockit, alors restez à l’écoute pour en apprendre davantage.

Et, comme toujours, vous pouvez trouver la mise en œuvre complète de ce tutoriel à l’adresse over .]

5.1. Articles de la série

Tous les articles de la série:

  • lien:/jmockit-101[JMockit 101]

  • lien:/jmockit-attentes[Un guide des attentes JMockit]

  • lien:/jmockit-advanced-usage[Rubriques avancées sur JMockit]