Einführung in EasyMock

1. Einleitung

In der Vergangenheit haben wir ausführlich über JMockit und Mockito gesprochen.

In diesem Tutorial geben wir eine Einführung in ein anderes Verspottungswerkzeug - EasyMock.

2. Maven-Abhängigkeiten

Bevor wir eintauchen, fügen wir unserer pom.xml die folgende Abhängigkeit hinzu :

 org.easymock easymock 3.5.1 test 

Die neueste Version finden Sie immer hier.

3. Kernkonzepte

Beim Generieren eines Mocks können wir das Zielobjekt simulieren, sein Verhalten angeben und schließlich überprüfen, ob es wie erwartet verwendet wird.

Die Arbeit mit EasyMock-Mocks umfasst vier Schritte:

  1. Erstellen eines Mocks der Zielklasse
  2. Aufzeichnen des erwarteten Verhaltens, einschließlich der Aktion, des Ergebnisses, der Ausnahmen usw.
  3. mit Mocks in Tests
  4. Überprüfen, ob es sich wie erwartet verhält

Nachdem unsere Aufnahme abgeschlossen ist, schalten wir sie in den Wiedergabemodus, sodass sich der Mock wie aufgezeichnet verhält, wenn Sie mit einem Objekt zusammenarbeiten, das ihn verwenden wird.

Schließlich überprüfen wir, ob alles wie erwartet verläuft.

Die vier oben genannten Schritte beziehen sich auf Methoden in org.easymock.EasyMock :

  1. mock (…) : Erzeugt ein Mock der Zielklasse, sei es eine konkrete Klasse oder eine Schnittstelle. Nach der Erstellung befindet sich ein Mock im Aufnahmemodus. Dies bedeutet, dass EasyMock alle Aktionen aufzeichnet, die das Mock-Objekt ausführt, und sie im Wiedergabemodus wiedergibt
  2. erwarten (…) : Mit dieser Methode können wir Erwartungen, einschließlich Aufrufe, Ergebnisse und Ausnahmen, für zugehörige Aufzeichnungsaktionen festlegen
  3. Wiedergabe (…) : Schaltet einen bestimmten Mock in den Wiedergabemodus. Jede Aktion, die zuvor aufgezeichnete Methodenaufrufe auslöst, gibt dann „aufgezeichnete Ergebnisse“ wieder.
  4. verify (…) : Überprüft, ob alle Erwartungen erfüllt wurden und ob kein unerwarteter Anruf für einen Mock ausgeführt wurde

Im nächsten Abschnitt zeigen wir anhand von Beispielen aus der Praxis, wie diese Schritte in Aktion funktionieren.

4. Ein praktisches Beispiel für Spott

Bevor wir fortfahren, werfen wir einen Blick auf den Beispielkontext: Angenommen, wir haben einen Leser des Baeldung-Blogs, der gerne Artikel auf der Website durchsucht, und dann versucht er / sie, Artikel zu schreiben.

Beginnen wir mit der Erstellung des folgenden Modells:

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 diesem Modell haben wir zwei private Mitglieder: den articleReader (eine konkrete Klasse) und den articleWriter (eine Schnittstelle).

Als nächstes werden wir sie verspotten, um das Verhalten von BaeldungReader zu überprüfen .

5. Mock With Java Code

Beginnen wir mit dem Verspotten eines ArticleReader .

5.1. Typisches Verspotten

Wir erwarten, dass die articleReader.next () -Methode aufgerufen wird, wenn ein Leser einen Artikel überspringt:

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

Im obigen Beispielcode halten wir uns strikt an das 4-Stufen-Verfahren und verspotten die ArticleReader- Klasse.

Obwohl es uns wirklich egal ist, was mockArticleReader.next () zurückgibt, müssen wir dennoch einen Rückgabewert für mockArticleReader.next () angeben, indem wir expected (…) .andReturn (…) verwenden.

Mit require (…) erwartet EasyMock, dass die Methode einen Wert zurückgibt oder eine Ausnahme auslöst.

Wenn wir es einfach tun:

mockArticleReader.next(); replay(mockArticleReader);

EasyMock wird sich darüber beschweren, da ein Aufruf bei Expect (…) .andReturn (…) erforderlich ist, wenn die Methode etwas zurückgibt.

Wenn es sich um eine ungültige Methode handelt, können wir ihre Aktion mit expectedLastCall () wie folgt erwarten :

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

5.2. Wiederholungsreihenfolge

Wenn Aktionen in einer bestimmten Reihenfolge wiedergegeben werden müssen, können wir strenger vorgehen:

@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 diesem Code - Schnipsel, wir verwenden strictMock (...) die Reihenfolge der Methodenaufrufe zu überprüfen . Bei Mocks, die mit mock (…) und strictMock (…) erstellt wurden , würden unerwartete Methodenaufrufe einen AssertionError verursachen .

Um einen Methodenaufruf für das Mock zuzulassen, können wir niceMock (…) verwenden :

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

Here we didn't expect the baeldungReader.readTopic(…) to be called, but EasyMock won't complain. With niceMock(…), EasyMock now only cares if the target object performed expected action or not.

5.3. Mocking Exception Throws

Now, let's continue with mocking the interface IArticleWriter, and how to handle expected Throwables:

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

In the snippet above, we expect the articleWriter is solid enough to detect XSS(Cross-site Scripting) attacks.

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.

Wie immer finden Sie die vollständige Implementierung auf Github.