Introduzione a EasyMock

1. Introduzione

In passato, abbiamo parlato a lungo di JMockit e Mockito.

In questo tutorial, daremo un'introduzione a un altro strumento di derisione: EasyMock.

2. Dipendenze di Maven

Prima di immergerci, aggiungiamo la seguente dipendenza al nostro pom.xml :

 org.easymock easymock 3.5.1 test 

L'ultima versione è sempre disponibile qui.

3. Concetti fondamentali

Quando si genera un mock, possiamo simulare l'oggetto target, specificarne il comportamento e infine verificare se viene utilizzato come previsto.

Lavorare con i mock di EasyMock prevede quattro passaggi:

  1. creando una simulazione della classe target
  2. registrare il comportamento previsto, inclusa l'azione, il risultato, le eccezioni, ecc.
  3. usando mock nei test
  4. verificando se si comporta come previsto

Al termine della registrazione, passiamo alla modalità "replay", in modo che il mock si comporti come registrato quando si collabora con qualsiasi oggetto che lo utilizzerà.

Alla fine, verifichiamo se tutto va come previsto.

I quattro passaggi sopra menzionati si riferiscono ai metodi in org.easymock.EasyMock :

  1. mock (…) : genera un mock della classe target, sia essa una classe concreta o un'interfaccia. Una volta creato, un mock è in modalità "registrazione", il che significa che EasyMock registrerà qualsiasi azione intrapresa dall'oggetto mock e li riprodurrà in modalità "replay"
  2. aspettarsi (...) : con questo metodo, possiamo impostare le aspettative, incluse chiamate, risultati ed eccezioni, per le azioni di registrazione associate
  3. replay (...) : cambia un dato mock in modalità "replay". Quindi, qualsiasi azione che attivi chiamate al metodo registrate in precedenza riprodurrà i "risultati registrati"
  4. verifica (…) : verifica che tutte le aspettative siano state soddisfatte e che nessuna chiamata inaspettata sia stata eseguita su un mock

Nella sezione successiva, mostreremo come funzionano questi passaggi in azione, utilizzando esempi del mondo reale.

4. Un esempio pratico di derisione

Prima di continuare, diamo un'occhiata al contesto di esempio: diciamo di avere un lettore del blog di Baeldung, a cui piace sfogliare gli articoli sul sito web, e poi cerca di scrivere articoli.

Cominciamo creando il seguente modello:

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); } }

In questo modello, abbiamo due membri privati: l' articoloReader (una classe concreta) e l' articoloWriter (un'interfaccia).

Successivamente, li derideremo per verificare il comportamento di BaeldungReader .

5. Mock con codice Java

Cominciamo con la derisione di un ArticleReader .

5.1. Tipico beffardo

Ci aspettiamo che il metodo articleReader.next () venga chiamato quando un lettore salta un articolo:

@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); }

Nel codice di esempio sopra, ci atteniamo strettamente alla procedura in 4 passaggi e deridiamo la classe ArticleReader .

Anche se davvero non ci interessa cosa restituisce mockArticleReader.next () , dobbiamo ancora specificare un valore di ritorno per mockArticleReader.next () usando wait (…) .andReturn (…).

Con attesa (...) , EasyMock si aspetta che il metodo restituisca un valore o generi un'eccezione .

Se facciamo semplicemente:

mockArticleReader.next(); replay(mockArticleReader);

EasyMock si lamenterà di questo, poiché richiede una chiamata in attesa (...) .andReturn (...) se il metodo restituisce qualcosa.

Se è un metodo void , possiamo aspettarci la sua azione usando waitLastCall () in questo modo:

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

5.2. Ordine di riproduzione

Se abbiamo bisogno che le azioni vengano riprodotte in un ordine specifico, possiamo essere più severi:

@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); }

In questo frammento, usiamo strictMock (…) per controllare l'ordine delle chiamate ai metodi . Per i mock creati da mock (...) e strictMock (...) , qualsiasi chiamata inaspettata di metodo causerebbe un AssertionError .

Per consentire qualsiasi chiamata di metodo per il mock, possiamo usare 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); }

Qui non ci aspettavamo che il baeldungReader.readTopic (…) venisse chiamato, ma EasyMock non si lamenterà. Con niceMock (…), EasyMock ora si preoccupa solo se l'oggetto target ha eseguito o meno l'azione prevista.

5.3. Lanci di eccezione beffardo

Ora, continuiamo a deridere l'interfaccia IArticleWriter e come gestire i Throwable previsti :

@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()); }

Nello snippet sopra, ci aspettiamo che l' articoloWriter sia abbastanza solido da rilevare gli attacchi 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.

Come sempre, l'implementazione completa può essere trovata su Github.