Pengantar EasyMock

1. Perkenalan

Di masa lalu, kami berbicara banyak tentang JMockit dan Mockito.

Dalam tutorial ini, kami akan memberikan pengantar ke alat mocking lainnya - EasyMock.

2. Ketergantungan Maven

Sebelum kita menyelami, mari tambahkan ketergantungan berikut ke pom.xml kita :

 org.easymock easymock 3.5.1 test 

Versi terbaru selalu dapat ditemukan di sini.

3. Konsep Inti

Saat membuat tiruan, kita bisa mensimulasikan objek target, menentukan perilakunya, dan terakhir memverifikasi apakah itu digunakan seperti yang diharapkan.

Bekerja dengan ejekan EasyMock melibatkan empat langkah:

  1. membuat tiruan kelas target
  2. merekam perilaku yang diharapkan, termasuk tindakan, hasil, pengecualian, dll.
  3. menggunakan ejekan dalam tes
  4. memverifikasi apakah itu berperilaku seperti yang diharapkan

Setelah perekaman kami selesai, kami mengalihkannya ke mode "replay", sehingga tiruan berperilaku seperti yang direkam saat berkolaborasi dengan objek apa pun yang akan menggunakannya.

Akhirnya, kami memverifikasi apakah semuanya berjalan seperti yang diharapkan.

Empat langkah yang disebutkan di atas terkait dengan metode di org.easymock.EasyMock :

  1. mock (…) : menghasilkan tiruan kelas target, baik itu kelas konkret atau antarmuka. Setelah dibuat, tiruan berada dalam mode "rekam", artinya EasyMock akan merekam tindakan apa pun yang dilakukan Objek Tiruan, dan memutarnya kembali dalam mode "putar ulang"
  2. ekspektasi (…) : dengan metode ini, kami dapat menetapkan ekspektasi, termasuk panggilan, hasil, dan pengecualian, untuk tindakan perekaman terkait
  3. replay (…) : mengalihkan tiruan yang diberikan ke mode "replay". Kemudian, tindakan apa pun yang memicu panggilan metode yang direkam sebelumnya akan memutar ulang "hasil yang direkam"
  4. Verifikasikan (…) : memverifikasi bahwa semua ekspektasi terpenuhi dan bahwa tidak ada panggilan tak terduga yang dilakukan sebagai tiruan

Di bagian selanjutnya, kami akan menunjukkan bagaimana langkah-langkah ini bekerja dalam tindakan, menggunakan contoh dunia nyata.

4. Contoh Praktis Mengolok-olok

Sebelum kita lanjut, mari kita lihat contoh konteksnya: misalnya kita memiliki pembaca blog Baeldung, yang suka melihat-lihat artikel di website, lalu mencoba menulis artikel.

Mari kita mulai dengan membuat model berikut:

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

Dalam model ini, kami memiliki dua anggota pribadi: articleReader (kelas konkret) dan articleWriter (antarmuka).

Selanjutnya, kami akan mengejek mereka untuk memverifikasi perilaku BaeldungReader .

5. Mock Dengan Kode Java

Mari kita mulai dengan mengejek ArticleReader .

5.1. Mengolok-olok Khas

Kami mengharapkan metode articleReader.next () dipanggil saat pembaca melewatkan artikel:

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

Dalam kode contoh di atas, kami berpegang teguh pada prosedur 4 langkah dan meniru kelas ArticleReader .

Meskipun kita benar-benar tidak peduli apa yang dikembalikan mockArticleReader.next () , kita masih perlu menetapkan nilai hasil untuk mockArticleReader.next () dengan menggunakan ekspektasi (…) .andReturn (…).

Dengan harapan (…) , EasyMock mengharapkan metode untuk mengembalikan nilai atau melempar Exception.

Jika kita hanya melakukan:

mockArticleReader.next(); replay(mockArticleReader);

EasyMock akan mengeluh tentang hal ini, karena memerlukan panggilan sesuai harapan (…) .andReturn (…) jika metode mengembalikan sesuatu.

Jika ini adalah metode void , kita bisa mengharapkan aksinya menggunakan expectLastCall () seperti ini:

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

5.2. Replay Order

Jika kami membutuhkan tindakan untuk diputar ulang dalam urutan tertentu, kami bisa lebih ketat:

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

Dalam potongan ini, kami menggunakan strictMock (…) untuk memeriksa urutan pemanggilan metode . Untuk tiruan yang dibuat oleh mock (…) dan strictMock (…) , setiap panggilan metode yang tidak terduga akan menyebabkan AssertionError .

Untuk mengizinkan panggilan metode apa pun untuk tiruan, kita dapat menggunakan 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); }

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.

Seperti biasa, implementasi penuh dapat ditemukan di Github.