Introduction à EasyMock

1. Introduction

Dans le passé, nous avons beaucoup parlé de JMockit et Mockito.

Dans ce tutoriel, nous allons donner une introduction à un autre outil de simulation - EasyMock.

2. Dépendances de Maven

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

 org.easymock easymock 3.5.1 test 

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

3. Concepts de base

Lors de la génération d'une maquette, nous pouvons simuler l'objet cible, spécifier son comportement et enfin vérifier s'il est utilisé comme prévu.

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

  1. création d'une maquette de la classe cible
  2. enregistrer son comportement attendu, y compris l'action, le résultat, les exceptions, etc.
  3. utiliser des simulations dans les tests
  4. vérifier s'il se comporte comme prévu

Une fois notre enregistrement terminé, nous le passons en mode «replay», de sorte que la maquette se comporte comme enregistrée lors de la collaboration 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 dans org.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 enregistrera toute action prise par l'objet maquette et la rejouera 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 (…) : fait passer une maquette donnée en mode «replay». Ensuite, toute action déclenchant des appels de méthode précédemment enregistrés rejouera 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, en utilisant des exemples du monde réel.

4. Un exemple pratique de moquerie

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

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

public class BaeldungReader { private ArticleReader articleReader; private IArticleWriter articleWriter; // constructors public BaeldungArticle 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: l' articleReader (une classe concrète) et l' articleWriter (une interface).

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

5. Simuler avec le code Java

Commençons par nous moquer d'un ArticleReader .

5.1. Mocking typique

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

@Test public void whenReadNext_thenNextArticleRead(){ ArticleReader mockArticleReader = mock(ArticleReader.class); BaeldungReader baeldungReader = new BaeldungReader(mockArticleReader); expect(mockArticleReader.next()).andReturn(null); replay(mockArticleReader); baeldungReader.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 classe ArticleReader .

Bien que nous ne nous soucions vraiment pas de ce que mockArticleReader.next () renvoie, nous devons toujours spécifier une valeur de retour pour mockArticleReader.next () en utilisant expect (…) .andReturn (…).

Avec expect (…) , EasyMock s'attend à ce que la méthode retourne une valeur ou lève une exception.

Si nous faisons simplement:

mockArticleReader.next(); replay(mockArticleReader);

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

Si c'est une méthode void , nous pouvons nous attendre à son action en utilisant expectLastCall () comme ceci:

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

5.2. Ordre de relecture

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

@Test public void whenReadNextAndSkimTopics_thenAllAllowed(){ ArticleReader mockArticleReader = strictMock(ArticleReader.class); BaeldungReade baeldungReader = new BaeldungReader(mockArticleReader); expect(mockArticleReader.next()).andReturn(null); expect(mockArticleReader.ofTopic("easymock")).andReturn(null); replay(mockArticleReader); baeldungReader.readNext(); baeldungReader.readTopic("easymock"); verify(mockArticleReader); }

Dans cet extrait de code , nous utilisons strictMock (…) pour vérifier l'ordre des appels de méthode . Pour les mocks créés par mock (…) et strictMock (…) , tout appel de méthode inattendu provoquerait une AssertionError .

Pour autoriser tout appel de méthode pour le mock, nous pouvons utiliser niceMock (…) :

@Test public void whenReadNextAndOthers_thenAllowed(){ ArticleReader mockArticleReader = niceMock(ArticleReader.class); BaeldungReade baeldungReader = new BaeldungReader(mockArticleReader); expect(mockArticleReader.next()).andReturn(null); replay(mockArticleReader); baeldungReader.readNext(); baeldungReader.readTopic("easymock"); verify(mockArticleReader); }

Ici, nous ne nous attendions pas à ce que le baeldungReader.readTopic (…) soit appelé, mais EasyMock ne se plaindra pas. Avec niceMock (…), EasyMock ne se soucie plus que si l'objet cible a effectué ou non l'action attendue.

5.3. Mocking Exception Lancers

Maintenant, continuons à nous moquer de l'interface IArticleWriter , et comment gérer les Throwables 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 que l' articleWriter soit suffisamment solide pour détecter les attaques XSS (Cross-site Scripting).

So when the reader tries to inject malicious code into the article content, the writer should throw an IllegalArgumentException. We recorded this expected behavior using expect(…).andThrow(…).

6. Mock With Annotation

EasyMock also supports injecting mocks using annotations. To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.

Let's rewrite previous snippets:

@RunWith(EasyMockRunner.class) public class BaeldungReaderAnnotatedTest { @Mock ArticleReader mockArticleReader; @TestSubject BaeldungReader baeldungReader = new BaeldungReader(); @Test public void whenReadNext_thenNextArticleRead() { expect(mockArticleReader.next()).andReturn(null); replay(mockArticleReader); baeldungReader.readNext(); verify(mockArticleReader); } }

Equivalent to mock(…), a mock will be injected into fields annotated with @Mock. And these mocks will be injected into fields of the class annotated with @TestSubject.

In the snippet above, we didn't explicitly initialize the articleReader field in baeldungReader. When calling baeldungReader.readNext(), we can inter that implicitly called mockArticleReader.

That was because mockArticleReader was injected to the articleReader field.

Note that if we want to use another test runner instead of EasyMockRunner, we can use the JUnit test rule EasyMockRule:

public class BaeldungReaderAnnotatedWithRuleTest { @Rule public EasyMockRule mockRule = new EasyMockRule(this); //... @Test public void whenReadNext_thenNextArticleRead(){ expect(mockArticleReader.next()).andReturn(null); replay(mockArticleReader); baeldungReader.readNext(); verify(mockArticleReader); } }

7. Mock With EasyMockSupport

Sometimes we need to introduce multiple mocks in a single test, and we have to repeat manually:

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

This is ugly, and we need an elegant solution.

Luckily, we have a class EasyMockSupport in EasyMock to help deal with this. It helps keep track of mocks, such that we can replay and verify them in a batch like this:

//... public class BaeldungReaderMockSupportTest 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); } }

Here we mocked both articleReader and articleWriter. When setting these mocks to “replay” mode, we used a static method replayAll() provided by EasyMockSupport, and used verifyAll() to verify their behaviors in batch.

We also introduced times(…) method in the expect phase. It helps specify how many times we expect the method to be called so that we can avoid introducing duplicate code.

We can also use EasyMockSupport through delegation:

EasyMockSupport easyMockSupport = new EasyMockSupport(); @Test public void whenReadAndWriteSequencially_thenWorks(){ ArticleReader mockArticleReader = easyMockSupport .createMock(ArticleReader.class); IArticleWriter mockArticleWriter = easyMockSupport .createMock(IArticleWriter.class); BaeldungReader baeldungReader = new BaeldungReader( mockArticleReader, mockArticleWriter); expect(mockArticleReader.next()).andReturn(null); expect(mockArticleWriter.write("title", "content")) .andReturn(""); easyMockSupport.replayAll(); baeldungReader.readNext(); baeldungReader.write("title", "content"); easyMockSupport.verifyAll(); }

Previously, we used static methods or annotations to create and manage mocks. Under the hood, these static and annotated mocks are controlled by a global EasyMockSupport instance.

Here, we explicitly instantiated it and take all these mocks under our own control, through delegation. This may help avoid confusion if there's any name conflicts in our test code with EasyMock or be there any similar cases.

8. Conclusion

In this article, we briefly introduced the basic usage of EasyMock, about how to generate mock objects, record and replay their behaviors, and verify if they behaved correctly.

In case you may be interested, check out this article for a comparison of EasyMock, Mocket, and JMockit.

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