Mocking: Suatu Kebohongan yang Mulia
Salah satu prinsip dari unit testing adalah Repeatable. Suatu unit test harus dapat dijalankan kapanpun tanpa tergantung pada environment testing dan hasilnya harus selalu sama. Hal ini tentu cukup sulit dilakukan mengingat sebagian besar program pasti tergantung pada suatu hal di luar program tersebut. Sebagai contoh, misalkan program kita membutuhkan akses ke suatu halaman web. Tidak mungkin kita dapat menjamin kembalian dari halaman web tersebut selalu sama, bahkan kita tidak dapat menjamin bahwa environment testing kita memiliki akses internet untuk mengakses halaman web tersebut. Jadi, jika kita hanya menjalankan unit test itu saja, hasilnya tidak akan deterministik, melanggar prinsip Repeatable.
Apa yang dapat kita lakukan untuk menanggulangi hal tersebut? “You can not forgive someone who speaks the truth when nobody needs that truth” — Karl Kraus. Program kita tidak memerlukan hal sebenarnya dalam kasus unit testing, karena kita hanya mengetes logika internal dari method kita saja, sesuai dengan definisi bahwa unit testing mengecek “unit”. Jadi kita bohongi program kita. Kita berikan hal palsu untuk sesuatu yang dibutuhkan oleh program, dimana hal palsu tersebut dapat kita kontrol sehingga memberikan hasil yang deterministik, sehingga unit test kita pun menjadi deterministik. Teknik yang paling sering digunakan untuk melakukan ini adalah mock, yang akan dibahas penerapannya pada Android dengan desain MVP menggunakan library Mockito.
Misalkan kita akan membuat suatu presenter yang dapat menyimpan data pada realm. Suatu presenter menghubungkan view dengan realm. Karena instansiasi view tergantung pada activity yang berjalan, kita tidak mungkin dapat menginstansiasi view tersebut untuk keperluan testing. Begitu pula dengan realm yang tidak mungkin bisa kita jalankan untuk suatu unit testing. Dengan demikian, kita perlu membohongi program kita dan membuat instance realm dan view untuk menjalankan testing, tetapi dengan method-method pada tiap instance tersebut sesuai dengan keperluan kita.
Anggap kita mau mengetes fungsi berikut pada presenter kita.
public void onSaveThing() {
Thing thing = new Thing();
getRealmDataManager().createOrUpdateThing(thing, this); getView().openMainActivity();
}
Dengan RealmDataManager adalah suatu objek buatan kita sendiri yang menangani akses langsung ke realm, dan presenter kita memiliki constructor yang membutuhkan RealmDataManager tersebut.
public ThingPresenter(final RealmDataManager realmDataManager) {
super(realmDataManager);
}
Tidak lupa bahwa presenter pasti terkait dengan suatu View.
public void onAttach(V view) {
this.view = view;
}
Telah disampaikan sebelumnya bahwa view dan realm kita tidak dapat diinstansiasi tanpa terpengaruh environment. Oleh karena itu, saat testing kita dapat melakukan mocking seperti ini untuk membentuk objek View dan RealmDataManager palsu.
@Mock
RealmDataManager mRealmDataManager;@Mock
ThingView thingView;
Dilanjutkan dengan memanggil initMocks pada setup kita.
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ThingPresenter thingPresenter = new
ThingPresenter(mRealmDataManager);
thingPresenter.onAttach(thingView);
}
Kelebihan dari metode mock ini adalah kita dapat memastikan bahwa method yang diinginkan terpanggil. Kita tahu bahwa fungsi save kita harus memanggil fungsi createOrUpdateThing pada objek RealmDataManager dan fungsi openMainActivity pada thingView. Maka, pada testing kita, kita dapat memastikan method tersebut terpanggil.
@Test
public void testOnSaveProductSuccess() {
thingPresenter.onSaveThing();
verify(mRealmDataManager, times(1))
.createOrUpdateThing(any(Thing.class), thingPresenter);
verify(thingView, times(1)).openMainActivity();
}
Contoh di atas menunjukkan bagaimana mock object membantu dalam instansiasi hal yang sensitif pada environment. Namun, contoh tersebut tidak menunjukkan bagaimana jika method pada objek tersebut akan mengembalikan suatu nilai yang tentu saja diperlukan pada beberapa kasus. Mock object dapat diatur agar mengembalikan suatu nilai yang telah didefinisikan saat suatu method dipanggil. Misalkan terdapat method getSomeString() pada RealmDataManager kita. Kita dapat mengatur object mock kita agar method tersebut mengembalikan nilai yang kita inginkan, misalnya seperti berikut.
when(mRealmDataManager.getSomeString()).thenReturn("test");
Demikian adalah cara membohongi program kita dengan melakukan mocking saat testing. Dengan melakukan mocking, unit test kita menjadi semakin isolated dan juga memenuhi kriteria determinisme agar menjadi Repeatable. Happy l̷y̷i̷n̷g̷ testing!