Panduan untuk Harapan JMockit

1. Pendahuluan

Artikel ini adalah angsuran kedua dalam seri JMockit. Anda mungkin ingin membaca artikel pertama karena kami berasumsi bahwa Anda sudah terbiasa dengan dasar-dasar JMockit.

Hari ini kita akan membahas lebih dalam dan fokus pada ekspektasi. Kami akan menunjukkan bagaimana mendefinisikan pencocokan argumen yang lebih spesifik atau umum dan cara yang lebih maju untuk mendefinisikan nilai.

2. Pencocokan Nilai Argumen

Pendekatan berikut berlaku baik untuk Harapan maupun Verifikasi .

2.1. Bidang "Apa Pun"

JMockit menawarkan sekumpulan bidang utilitas untuk membuat pencocokan argumen menjadi lebih umum. Salah satu utilitas ini adalah bidang anyX .

Ini akan memeriksa bahwa setiap nilai telah diteruskan dan ada satu untuk setiap tipe primitif (dan kelas pembungkus yang sesuai), satu untuk string, dan satu "universal" dari tipe Object .

Mari kita lihat contohnya:

public interface ExpectationsCollaborator { String methodForAny1(String s, int i, Boolean b); void methodForAny2(Long l, List lst); } @Test public void test(@Mocked ExpectationsCollaborator mock) throws Exception { new Expectations() {{ mock.methodForAny1(anyString, anyInt, anyBoolean); result = "any"; }}; Assert.assertEquals("any", mock.methodForAny1("barfooxyz", 0, Boolean.FALSE)); mock.methodForAny2(2L, new ArrayList()); new FullVerifications() {{ mock.methodForAny2(anyLong, (List) any); }}; }

Anda harus memperhitungkan bahwa saat menggunakan bidang apa pun , Anda perlu mentransmisikannya ke jenis yang diharapkan. Daftar lengkap bidang ada dalam dokumentasi.

2.2. Metode “Dengan”

JMockit juga menyediakan beberapa metode untuk membantu pencocokan argumen umum. Itu adalah metode withX .

Ini memungkinkan pencocokan lebih lanjut daripada bidang anyX . Kita dapat melihat contoh di sini di mana kita akan mendefinisikan ekspektasi untuk metode yang akan dipicu dengan string yang berisi foo , integer tidak sama dengan 1, Boolean bukan-null, dan instance kelas List apa pun :

public interface ExpectationsCollaborator { String methodForWith1(String s, int i); void methodForWith2(Boolean b, List l); } @Test public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception { new Expectations() {{ mock.methodForWith1(withSubstring("foo"), withNotEqual(1)); result = "with"; }}; assertEquals("with", mock.methodForWith1("barfooxyz", 2)); mock.methodForWith2(Boolean.TRUE, new ArrayList()); new Verifications() {{ mock.methodForWith2(withNotNull(), withInstanceOf(List.class)); }}; }

Anda dapat melihat daftar lengkap metode withX pada dokumentasi JMockit.

Perhatikan bahwa spesial dengan (Delegasi) dan withArgThat (Matcher) akan tercakup dalam subbagian mereka sendiri.

2.3. Null Is Not Null

Sesuatu yang baik untuk dipahami lebih cepat daripada nanti adalah bahwa null tidak digunakan untuk mendefinisikan argumen yang null telah diteruskan ke tiruan.

Sebenarnya, null digunakan sebagai gula sintaksis untuk menentukan bahwa objek apa pun akan diteruskan (sehingga hanya dapat digunakan untuk parameter tipe referensi). Untuk secara khusus memverifikasi bahwa parameter tertentu menerima referensi null , pencocokan withNull () bisa digunakan.

Untuk contoh berikutnya, kita akan menentukan perilaku tiruan, yang harus dipicu ketika argumen yang diteruskan adalah: string apa pun, Daftar apa pun, dan referensi null :

public interface ExpectationsCollaborator { String methodForNulls1(String s, List l); void methodForNulls2(String s, List l); } @Test public void testWithNulls(@Mocked ExpectationsCollaborator mock){ new Expectations() {{ mock.methodForNulls1(anyString, null); result = "null"; }}; assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList())); mock.methodForNulls2("blablabla", null); new Verifications() {{ mock.methodForNulls2(anyString, (List) withNull()); }}; }

Perhatikan perbedaannya: null berarti daftar apa pun dan withNull () berarti referensi null ke daftar. Secara khusus, ini menghindari kebutuhan untuk mentransmisikan nilai ke tipe parameter yang dideklarasikan (lihat bahwa argumen ketiga harus dilemparkan tetapi bukan yang kedua).

Satu-satunya syarat untuk dapat menggunakan ini adalah bahwa setidaknya satu pencocokan argumen eksplisit telah digunakan untuk ekspektasi (baik metode with atau bidang apa pun ).

2.4. Bidang "Times"

Terkadang, kami ingin membatasi jumlah pemanggilan yang diharapkan untuk metode tiruan. Untuk ini, JMockit memiliki kata-kata yang dicadangkan kali , minTimes dan maxTimes (ketiganya hanya mengizinkan bilangan bulat non-negatif).

public interface ExpectationsCollaborator { void methodForTimes1(); void methodForTimes2(); void methodForTimes3(); } @Test public void testWithTimes(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodForTimes1(); times = 2; mock.methodForTimes2(); }}; mock.methodForTimes1(); mock.methodForTimes1(); mock.methodForTimes2(); mock.methodForTimes3(); mock.methodForTimes3(); mock.methodForTimes3(); new Verifications() {{ mock.methodForTimes3(); minTimes = 1; maxTimes = 3; }}; }

Dalam contoh ini, kita telah mendefinisikan bahwa tepat dua pemanggilan (bukan satu, bukan tiga, tepat dua) dari methodForTimes1 () harus dilakukan menggunakan baris times = 2; .

Kemudian kami menggunakan perilaku default (jika tidak ada batasan pengulangan yang diberikan minTimes = 1; digunakan) untuk menentukan bahwa setidaknya satu pemanggilan akan dilakukan ke methodForTimes2 ().

Terakhir, menggunakan minTimes = 1; diikuti oleh maxTimes = 3; kita mendefinisikan bahwa antara satu dan tiga pemanggilan akan terjadi ke methodForTimes3 () .

Perhatikan bahwa minTimes dan maxTimes dapat ditentukan untuk ekspektasi yang sama, selama minTimes ditetapkan terlebih dahulu. Di sisi lain, waktu hanya bisa digunakan sendiri.

2.5. Pencocokan Argumen Kustom

Terkadang pencocokan argumen tidak sesederhana menentukan nilai atau menggunakan beberapa utilitas yang telah ditentukan ( anyX atau withX ).

Untuk kasus tersebut, JMockit bergantung pada antarmuka Matcher Hamcrest . Anda hanya perlu menentukan matcher untuk skenario pengujian tertentu dan menggunakan matcher tersebut dengan panggilan withArgThat () .

Mari kita lihat contoh untuk mencocokkan kelas tertentu dengan objek yang diteruskan:

public interface ExpectationsCollaborator { void methodForArgThat(Object o); } public class Model { public String getInfo(){ return "info"; } } @Test public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodForArgThat(withArgThat(new BaseMatcher() { @Override public boolean matches(Object item) { return item instanceof Model && "info".equals(((Model) item).getInfo()); } @Override public void describeTo(Description description) { } })); }}; mock.methodForArgThat(new Model()); }

3. Mengembalikan Nilai

Sekarang mari kita lihat nilai yang dikembalikan; perlu diingat bahwa pendekatan berikut hanya berlaku untuk Ekspektasi karena tidak ada nilai kembali yang dapat ditentukan untuk Verifikasi .

3.1. Hasil dan Pengembalian (…)

Saat menggunakan JMockit, Anda memiliki tiga cara berbeda untuk menentukan hasil yang diharapkan dari pemanggilan metode tiruan. Dari ketiganya, sekarang kita akan berbicara tentang dua yang pertama (yang paling sederhana) yang pasti akan mencakup 90% kasus penggunaan sehari-hari.

These two are the result field and the returns(Object…) method:

  • With the result field, you can define one return value for any non-void returning mocked method. This return value can also be an exception to be thrown (this time working for both non-void and void returning methods).
    • Several result field assignations can be done in order to return more than one value for more than one method invocations (you can mix both return values and errors to be thrown).
    • The same behaviour will be achieved when assigning to result a list or an array of values (of the same type than the return type of the mocked method, NO exceptions here).
  • The returns(Object…) method is syntactic sugar for returning several values of the same time.

This is more easily shown with a code snippet:

public interface ExpectationsCollaborator{ String methodReturnsString(); int methodReturnsInt(); } @Test public void testResultAndReturns(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodReturnsString(); result = "foo"; result = new Exception(); result = "bar"; returns("foo", "bar"); mock.methodReturnsInt(); result = new int[]{1, 2, 3}; result = 1; }}; assertEquals("Should return foo", "foo", mock.methodReturnsString()); try { mock.methodReturnsString(); fail("Shouldn't reach here"); } catch (Exception e) { // NOOP } assertEquals("Should return bar", "bar", mock.methodReturnsString()); assertEquals("Should return 1", 1, mock.methodReturnsInt()); assertEquals("Should return 2", 2, mock.methodReturnsInt()); assertEquals("Should return 3", 3, mock.methodReturnsInt()); assertEquals("Should return foo", "foo", mock.methodReturnsString()); assertEquals("Should return bar", "bar", mock.methodReturnsString()); assertEquals("Should return 1", 1, mock.methodReturnsInt()); }

In this example, we have defined that for the first three calls to methodReturnsString() the expected returns are (in order) “foo”, an exception and “bar”. We achieved this using three different assignations to the result field.

Then on line 14, we defined that for the fourth and fifth calls, “foo” and “bar” should be returned using the returns(Object…) method.

For the methodReturnsInt() we defined on line 13 to return 1, 2 and lastly 3 by assigning an array with the different results to the result field and on line 15 we defined to return 1 by a simple assignation to the result field.

As you can see there are several ways of defining return values for mocked methods.

3.2. Delegators

To end the article we're going to cover the third way of defining the return value: the Delegate interface. This interface is used for defining more complex return values when defining mocked methods.

We're going to see an example to simply the explaining:

public interface ExpectationsCollaborator { int methodForDelegate(int i); } @Test public void testDelegate(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodForDelegate(anyInt); result = new Delegate() { int delegate(int i) throws Exception { if (i < 3) { return 5; } else { throw new Exception(); } } }; }}; assertEquals("Should return 5", 5, mock.methodForDelegate(1)); try { mock.methodForDelegate(3); fail("Shouldn't reach here"); } catch (Exception e) { } } 

The way to use a delegator is to create a new instance for it and assign it to a returns field. In this new instance, you should create a new method with the same parameters and return type than the mocked method (you can use any name for it). Inside this new method, use whatever implementation you want in order to return the desired value.

In the example, we did an implementation in which 5 should be returned when the value passed to the mocked method is less than 3 and an exception is thrown otherwise (note that we had to use times = 2; so that the second invocation is expected as we lost the default behaviour by defining a return value).

Ini mungkin tampak seperti kode yang cukup banyak, tetapi untuk beberapa kasus, itu akan menjadi satu-satunya cara untuk mencapai hasil yang kita inginkan.

4. Kesimpulan

Dengan ini, kami secara praktis menunjukkan semua yang kami butuhkan untuk menciptakan ekspektasi dan verifikasi untuk tes harian kami.

Kami tentu saja akan menerbitkan lebih banyak artikel di JMockit, jadi pantau terus untuk mempelajari lebih lanjut.

Dan, seperti biasa, implementasi lengkap dari tutorial ini dapat ditemukan di proyek GitHub.

4.1. Artikel dalam Seri

Semua artikel dari seri:

  • JMockit 101
  • Panduan untuk Harapan JMockit
  • Penggunaan Lanjutan JMockit