Introduction à EasyMock

Introduction à EasyMock

1. introduction

Dans le passé, nous avons beaucoup parlé deJMockit etMockito.

Dans ce didacticiel, nous présenterons un autre outil de simulation -EasyMock.

2. Dépendances Maven

Avant de plonger, ajoutons la dépendance suivante à nospom.xml:


    org.easymock
    easymock
    3.5.1
    test

La dernière version peut toujours être trouvéehere.

3. Concepts de base

Lors de la génération d'une maquette,we can simulate the target object, specify its behavior, and finally verify whether it’s used as expected.

Travailler avec les simulations d'EasyMock implique quatre étapes:

  1. créer une maquette de la classe cible

  2. enregistrer son comportement attendu, y compris l'action, le résultat, les exceptions, etc.

  3. utiliser des simulacres dans des tests

  4. vérifier s'il se comporte comme prévu

Une fois l’enregistrement terminé, nous passons en mode «lecture» afin que le simulacre se comporte de la même manière que lorsqu’il collabore avec tout objet qui l’utilisera.

Finalement, nous vérifions si tout se passe comme prévu.

Les quatre étapes mentionnées ci-dessus concernent les méthodes enorg.easymock.EasyMock:

  1. mock(…): génère une maquette de la classe cible, que ce soit une classe concrète ou une interface. Une fois créée, une maquette est en mode «enregistrement», ce qui signifie qu'EasyMock enregistre toutes les actions entreprises par l'objet simulé et les rejoue en mode «relecture».

  2. expect(…): avec cette méthode, nous pouvons définir des attentes, y compris les appels, les résultats et les exceptions, pour les actions d'enregistrement associées

  3. replay(…): bascule une maquette donnée en mode «replay». Ensuite, toute action déclenchant des appels de méthode enregistrés précédemment relira les «résultats enregistrés».

  4. verify(…): vérifie que toutes les attentes ont été satisfaites et qu'aucun appel inattendu n'a été effectué sur un simulacre **

Dans la section suivante, nous montrerons comment ces étapes fonctionnent en action, à l'aide d'exemples concrets.

4. Un exemple pratique de moquerie

Avant de continuer, examinons l'exemple de contexte: disons que nous avons un lecteur de l'exemple de blog, qui aime parcourir les articles sur le site Web, puis il / elle essaie d'écrire des articles.

Commençons par créer le modèle suivant:

public class exampleReader {

    private ArticleReader articleReader;
    private IArticleWriter articleWriter;

    // constructors

    public exampleArticle readNext(){
        return articleReader.next();
    }

    public List readTopic(String topic){
        return articleReader.ofTopic(topic);
    }

    public String write(String title, String content){
        return articleWriter.write(title, content);
    }
}

Dans ce modèle, nous avons deux membres privés: lesarticleReader (une classe concrète) et lesarticleWriter (une interface).

Ensuite, nous nous moquerons d'eux pour vérifier le comportement deexampleReader.

5. Mock avec Java Code

Commençons par nous moquer d'unArticleReader.

5.1. Mocking typique

Nous nous attendons à ce que la méthodearticleReader.next() soit appelée lorsqu'un lecteur saute un article:

@Test
public void whenReadNext_thenNextArticleRead(){
    ArticleReader mockArticleReader = mock(ArticleReader.class);
    exampleReader exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();

    verify(mockArticleReader);
}

Dans l'exemple de code ci-dessus, nous nous en tenons strictement à la procédure en 4 étapes et nous nous moquons de la classeArticleReader.

Although we really don’t care what mockArticleReader.next() returns, we still need to specify a return value for mockArticleReader.next() en utilisantexpect(…).andReturn(…).

Avecexpect(…), EasyMock s'attend à ce que la méthode renvoie une valeur ou renvoie unException.

Si nous faisons simplement:

mockArticleReader.next();
replay(mockArticleReader);

EasyMock s'en plaindra, car il nécessite un appel surexpect(…).andReturn(…) si la méthode renvoie quelque chose.

S'il s'agit d'une méthodevoid, nous pouvonsexpect son action en utilisantexpectLastCall() comme ceci:

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. Ordre de relecture

Si nous avons besoin que des actions soient rejouées dans un ordre spécifique, nous pouvons être plus stricts:

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
    ArticleReader mockArticleReader
      = strictMock(ArticleReader.class);
    exampleReade exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

Dans cet extrait, noususe strictMock(…) to check the order of method calls. Pour les simulations créées parmock(…) etstrictMock(…), tout appel de méthode inattendu provoquerait unAssertionError.

Pour autoriser tout appel de méthode pour la simulation, nous pouvons utiliserniceMock(…):

@Test
public void whenReadNextAndOthers_thenAllowed(){
    ArticleReader mockArticleReader = niceMock(ArticleReader.class);
    exampleReade exampleReader = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

Ici, nous ne nous attendions pas à ce que lesexampleReader.readTopic(…) soient appelés, mais EasyMock ne se plaindra pas. AvecniceMock(…),, EasyMock ne se soucie plus que si l'objet cible a effectué ou non l'action attendue.

5.3. MockingException jette

Maintenant, continuons à nous moquer de l'interfaceIArticleWriter et comment gérer lesThrowables attendus:

@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
    // mocking and initialization

    expect(mockArticleWriter
      .write("easymock",""))
      .andThrow(new IllegalArgumentException());
    replay(mockArticleWriter);

    // write malicious content and capture exception as expectedException

    verify(mockArticleWriter);
    assertEquals(
      IllegalArgumentException.class,
      expectedException.getClass());
}

Dans l'extrait ci-dessus, nous nous attendons à ce quearticleWriter soit suffisamment solide pour détecter les attaquesXSS(Cross-site Scripting).

Ainsi, lorsque le lecteur essaie d'injecter du code malveillant dans le contenu de l'article, le rédacteur doit lancer unIllegalArgumentException. Nous avons enregistré ce comportement attendu en utilisantexpect(…).andThrow(…).

6. Mock avec annotation

EasyMock prend également en charge l’injection de modèles à l’aide d’annotations. To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.

Réécrivons les extraits précédents:

@RunWith(EasyMockRunner.class)
public class exampleReaderAnnotatedTest {

    @Mock
    ArticleReader mockArticleReader;

    @TestSubject
    exampleReader exampleReader = new exampleReader();

    @Test
    public void whenReadNext_thenNextArticleRead() {
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }
}

Équivalent àmock(…), un simulacre sera injecté dans les champs annotés avec@Mock. Et ces simulations seront injectées dans les champs de la classe annotés avec@TestSubject.

Dans l'extrait de code ci-dessus, nous n'avons pas initialisé explicitement le champarticleReader dansexampleReader.. Lors de l'appel deexampleReader.readNext(), nous pouvons inter ce qui a appelé implicitementmockArticleReader.

En effet,mockArticleReader a été injecté dans le champthe articleReader.

Notez que si nous voulons utiliser un autre lanceur de test au lieu deEasyMockRunner, nous pouvons utiliser la règle de test JUnitEasyMockRule:

public class exampleReaderAnnotatedWithRuleTest {

    @Rule
    public EasyMockRule mockRule = new EasyMockRule(this);

    //...

    @Test
    public void whenReadNext_thenNextArticleRead(){
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }

}

7. Mock avecEasyMockSupport

Parfois, nous devons introduire plusieurs simulacres dans un seul test, et nous devons répéter manuellement:

replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);

C'est moche, et nous avons besoin d'une solution élégante.

Heureusement, nous avons une classeEasyMockSupport in EasyMock pour vous aider à résoudre ce problème. C'esthelps keep track of mocks, such that we can replay and verify them inun lot comme ceci:

//...
public class exampleReaderMockSupportTest extends EasyMockSupport{

    //...

    @Test
    public void whenReadAndWriteSequencially_thenWorks(){
        expect(mockArticleReader.next()).andReturn(null)
          .times(2).andThrow(new NoSuchElementException());
        expect(mockArticleWriter.write("title", "content"))
          .andReturn("BAEL-201801");
        replayAll();

        // execute read and write operations consecutively

        verifyAll();

        assertEquals(
          NoSuchElementException.class,
          expectedException.getClass());
        assertEquals("BAEL-201801", articleId);
    }

}

Ici, nous nous sommes moqués à la fois dearticleReader etarticleWriter. Lors de la mise en mode «replay» de ces simulacres, nous avons utilisé une méthode statiquereplayAll() fournie parEasyMockSupport, et utiliséverifyAll() pour vérifier leurs comportements en batch.

Nous avons également introduit la méthodetimes(…) dans la phaseexpect. Cela permet de spécifier combien de fois nous nous attendons à ce que la méthode soit appelée afin d'éviter d'introduire du code dupliqué.

Nous pouvons également utiliserEasyMockSupport par délégation:

EasyMockSupport easyMockSupport = new EasyMockSupport();

@Test
public void whenReadAndWriteSequencially_thenWorks(){
    ArticleReader mockArticleReader = easyMockSupport
      .createMock(ArticleReader.class);
    IArticleWriter mockArticleWriter = easyMockSupport
      .createMock(IArticleWriter.class);
    exampleReader exampleReader = new exampleReader(
      mockArticleReader, mockArticleWriter);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleWriter.write("title", "content"))
      .andReturn("");
    easyMockSupport.replayAll();

    exampleReader.readNext();
    exampleReader.write("title", "content");

    easyMockSupport.verifyAll();
}

Auparavant, nous utilisions des méthodes ou des annotations statiques pour créer et gérer des simulacres. Sous le capot, ces simulations statiques et annotées sont contrôlées par une instance globaleEasyMockSupport.

Ici, nous l'avons explicitement instancié et prenons tous ces simulacres sous notre propre contrôle, par délégation. Cela peut aider à éviter toute confusion en cas de conflit de nom dans notre code de test avec EasyMock ou dans des cas similaires.

8. Conclusion

Dans cet article, nous avons brièvement présenté l'utilisation de base d'EasyMock, à savoir comment générer des objets fictifs, enregistrer et rejouer leurs comportements et vérifier s'ils se sont bien comportés.

Au cas où vous seriez intéressé, consultezthis article pour une comparaison entre EasyMock, Mocket et JMockit.

Comme toujours, l'implémentation complète peut être trouvéeover on Github.