Pengenalan kepada EasyMock

1. Pengenalan

Pada masa lalu, kita telah banyak membincangkan JMockit dan Mockito.

Dalam tutorial ini, kami akan memberi pengenalan kepada alat mengejek lain - EasyMock.

2. Pergantungan Maven

Sebelum kita masuk, mari tambahkan kebergantungan berikut ke pom.xml kami :

 org.easymock easymock 3.5.1 test 

Versi terbaru boleh didapati di sini.

3. Konsep Teras

Semasa menghasilkan tiruan, kita dapat mensimulasikan objek sasaran, menentukan perilakunya, dan akhirnya mengesahkan sama ada ia digunakan seperti yang diharapkan.

Bekerja dengan ejekan EasyMock melibatkan empat langkah:

  1. mencipta ejekan kelas sasaran
  2. merakam tingkah laku yang diharapkan, termasuk tindakan, hasil, pengecualian, dll.
  3. menggunakan ejekan dalam ujian
  4. mengesahkan sama ada ia berkelakuan seperti yang diharapkan

Setelah rakaman kami selesai, kami beralih ke mod "replay", sehingga tiruan berperilaku seperti yang direkam ketika berkolaborasi dengan objek apa pun yang akan menggunakannya.

Akhirnya, kami mengesahkan sama ada semuanya berjalan seperti yang diharapkan.

Empat langkah yang dinyatakan di atas berkaitan dengan kaedah dalam org.easymock.EasyMock :

  1. mock (…) : menghasilkan tiruan dari kelas sasaran, sama ada kelas konkrit atau antara muka. Setelah dibuat, tiruan berada dalam mod "rakaman", yang berarti EasyMock akan merekam setiap tindakan yang dilakukan oleh Objek Mock, dan memutarnya kembali dalam mod "replay"
  2. mengharapkan (…) : dengan kaedah ini, kita dapat menetapkan harapan, termasuk panggilan, hasil, dan pengecualian, untuk tindakan rakaman yang berkaitan
  3. main semula (…) : menukar tiruan tertentu ke mod “replay”. Kemudian, setiap tindakan yang mencetuskan panggilan kaedah yang dirakam sebelumnya akan memainkan semula "hasil yang dirakam"
  4. verifikasi (…) : mengesahkan bahawa semua jangkaan dipenuhi dan tidak ada panggilan yang tidak dijangka dilakukan secara ejekan

Di bahagian seterusnya, kami akan menunjukkan bagaimana langkah-langkah ini berfungsi, menggunakan contoh dunia nyata.

4. Contoh Amalan Mengejek

Sebelum kita meneruskan, mari kita lihat konteks contohnya: katakanlah kita mempunyai pembaca blog Baeldung, yang suka melayari artikel di laman web, dan kemudian dia berusaha menulis artikel.

Mari mulakan 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 mempunyai dua anggota peribadi: artikelReader (kelas konkrit) dan artikelWriter (antara muka).

Seterusnya, kami akan mengejek mereka untuk mengesahkan tingkah laku BaeldungReader .

5. Mengejek dengan Java Code

Mari kita mulakan dengan mengejek Artikel Pembaca .

5.1. Mengejek Khas

Kami menjangkakan kaedah articleReader.next () akan dipanggil apabila pembaca melangkau 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 contoh kod di atas, kami berpegang teguh pada prosedur 4 langkah dan mengejek kelas ArticleReader .

Walaupun kita benar-benar tidak peduli apa yang dikembalikan mockArticleReader.next () , kita masih perlu menentukan nilai pengembalian untuk mockArticleReader.next () dengan menggunakan expect (…) .andReturn (…).

Dengan jangkaan (…) , EasyMock mengharapkan kaedah untuk mengembalikan nilai atau membuang Pengecualian.

Sekiranya kita hanya melakukan:

mockArticleReader.next(); replay(mockArticleReader);

EasyMock akan mengeluh tentang perkara ini, kerana ia memerlukan panggilan pada jangkaan (…). Dan Kembalinya (…) jika kaedah mengembalikan apa-apa.

Sekiranya kaedah tersebut tidak sah , kita boleh mengharapkan tindakannya menggunakan expectLastCall () seperti ini:

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

5.2. Main semula Pesanan

Sekiranya kita memerlukan tindakan untuk dimainkan dalam urutan tertentu, kita dapat menjadi lebih tegas:

@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 coretan ini, kami menggunakan ketatMock (…) untuk memeriksa urutan panggilan kaedah . Untuk ejekan yang dibuat oleh mock (…) dan tightMock (…) , sebarang panggilan kaedah yang tidak dijangka akan menyebabkan AssertionError .

Untuk membenarkan sebarang kaedah memanggil 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, pelaksanaan sepenuhnya dapat dilihat di Github.