Pendahuluan

Didalam membuat unit testing , umumnya kita menemukan 2 jenis tipe Test Double :

  • Mock
  • Stub

Jenis Test Double lainnya seperti Dummy,Fake, dan Spy, biasanya jarang kita temui.

Dua jenis tipe Test Double yang terkenal ini, yaitu Mock dan Stub, sering digunakan karena mudah dari sisi penggunaan karena biasanya sudah termasuk dalam library Unit test yang kita pakai.

Walaupun 2 jenis tipe test ini terkenal dan semirip, akan tetapi Mock dan Stub itu adalah 2 Jenis Test Double yang berbeda.

Kita terkadang menyamakan keduanya, padahal keduanya berbeda.

Ya jelas beda…, dari namanya juga sudah kelihatan bedanya..



Hmm..kalau begitu apakah perbedaannya ?

Secara konseptual, kedua jenis Test Double yang terkenal ini, yaitu Mock dan Stub, mempunyai perbedaan :

  • Mock itu adalah jenis Test Double dengan tujuan verifikasi behavior/fungsi dari objek.
  • Stub itu adalah jenis Test Double dengan tujuan verifikasi state/context dari objek.

Hmm, apa bisa ditulis dengan bahasa yang lebih mudah dipahami ?

Ok..ok.., kita coba dengan bahasa yang sederhana…

  • Mock itu adalah jenis Test Double untuk verifikasi fungsi.
  • Stub itu adalah jenis Test Double untuk verifikasi data.

Hmmm.. bisa lebih jelas lagi ???

Baiklah, coba kita bedah atu-atu :


1. Mock

Mock adalah jenis Test Double untuk mengimitasi atau meniru objek.

Dengan Mock, maka kita bisa membuat object tiruan yang menggantikan objek aslinya. Entah itu dengan membuat object Proxy, memakai Reflection, atau membuat objek terpisah yang kemudian dimapping oleh ClassLoader nantinya.

Secara konsep, object Mock ini bertujuan untuk memastikan bahwa objek tiruan ini tetap akan memanggil fungsi seperti fungsi aslinya, dan tentunya sesuai dengan ekspektasi.

Caranya bisa dengan melakukan delegasi pemanggilan fungsi, atau memanggil fungsi dari objek tiruan itu sendiri.

Di dalam terminologi Unit Test, ini disebut behavior verification / verifikasi fungsi.


Misalnya kita mempunyai code di Java, class OrderService di sebuah aplikasi e-commerce :

 1package com.example.demo;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.stereotype.Service;
 5
 6@Service
 7public class OrderServiceImpl implements OrderService {
 8
 9	@Autowired
10	private OrderRepository orderRepository;
11
12	@Override
13	public int order(String namaBarang, int jumlah) {
14		return orderRepository.orderByNameAndCount(namaBarang, jumlah);
15	}
16}

Lalu, kita membuat Unit Test untuk class diatas, sbb :

 1package com.example.demo;
 2
 3import static org.mockito.Mockito.times;
 4import static org.mockito.Mockito.verify;
 5
 6import org.junit.Before;
 7import org.junit.jupiter.api.Test;
 8import org.junit.jupiter.api.extension.ExtendWith;
 9import org.mockito.InjectMocks;
10import org.mockito.Mock;
11import org.mockito.MockitoAnnotations;
12import org.mockito.junit.jupiter.MockitoExtension;
13
14@ExtendWith(MockitoExtension.class)
15public class OrderServiceTest {
16
17	@InjectMocks
18	private OrderService orderService = new OrderServiceImpl();
19
20	@Mock
21	private OrderRepository orderRepository;
22
23	@Before
24	public void init() {
25		MockitoAnnotations.openMocks(this);
26	}
27
28	@Test
29	public void testOrder1() {
30		// exercise/execute
31		orderService.order("handphone", 1);
32
33		// assert/verify
34		verify(orderRepository,times(1)).orderByNameAndCount("handphone", 1);
35	}
36}

Apa yang terjadi ?

  • Kita melakukan mocking terhadap object OrderRepository., (line 20 dan 21)
  • Kita melakukan set-up terhadap object OrderService dan object OrderRepository., (line 25)
  • Kita melakukan eksekusi terhadap fungsi order(namabarang, jumlah) terhadap objek orderService, (line 31)
  • Dan terakhir kita melakukan verifikasi terhadap bahwa fungsi orderByNameAndCount(namabarang, jumlah) di objek mocking orderRepository, (line 34), benar-benar dipanggil, sebagai akibat kita memanggil fungsi order(namabarang, jumlah) di objek orderService.

Yang menjadi koentji dari Mock adalah step keempat diatas, yaitu Verifikasi Fungsi.

Kalau kita mengacu kepada konsep Mock Object di buku Gerard Meszaros yang berjudul XUnit Test Pattern, Refactoring Test Code,

maka ada 3 tahapan di dalam melakukan Mock, yaitu :

flowchart LR a0(Setup)-->a1(Expectation)-->a2(Exercise/eksekusi)-->a3(Verify)

Yang dimaksud dengan stepExpectation di flow ini adalah expectation terhadap fungsi dan argument apa saja yang diharapakan diterima oleh object Mock ini.

Biasanya expectation ini sudah otomatis dihandle oleh library Unit Test, seperti Mockito.

Jadi kesimpulannya, object Mock ini bertujuan memastikan bahwa fungsi yang kita harapkan dipanggil, akan dijalankan sesuai dengan ekspektasi kita.


2. Stub

Stub adalah jenis Test Double untuk memanipulasi data/context dari sebuah object Test Double.

Jadi yang dimanipulasi bukanlah fungsinya atau behaviournya, akan tetapi data/contextnya.

Gunanya untuk apa ?

  • sebagai titik kontrol/kendali dalam mengetes fungsi dengan data/context yang berbeda.

Secara konsep, Stub ini bertujuan untuk memastikan bahwa kita bisa mengetes object yang kita test dengan rentang data dan rentang context yang kita inginkan

Misalnya :

  • Kita ingin mengetes dengan data Integer bernilai maksimum, atau dengan data Integer bernilai mininum, atau dengan data yang berada tepat di batas nilai bilangan yang diterima oleh fungsinya.
  • Kita ingin mengetes dengan kombinasi banyak data sehingga cukup meyakinkan kita mengenai robustness dari code kita.

Contoh : Misalnya kita kita membuat Unit Test untuk class diatas lagi, sbb :

 1package com.example.demo;
 2
 3import static org.junit.jupiter.api.Assertions.assertEquals;
 4import static org.mockito.ArgumentMatchers.any;
 5import static org.mockito.Mockito.when;
 6
 7import org.junit.Before;
 8import org.junit.jupiter.api.Test;
 9import org.junit.jupiter.api.extension.ExtendWith;
10import org.mockito.InjectMocks;
11import org.mockito.Mock;
12import org.mockito.MockitoAnnotations;
13import org.mockito.junit.jupiter.MockitoExtension;
14
15@ExtendWith(MockitoExtension.class)
16public class OrderServiceTest {
17
18	@InjectMocks
19	private OrderService orderService = new OrderServiceImpl();
20
21	@Mock
22	private OrderRepository orderRepository;
23
24	@Before
25	public void init() {
26		MockitoAnnotations.openMocks(this);
27	}
28
29	@Test
30	public void testOrder1() {
31		// stubbing
32		when(orderRepository.orderByNameAndCount(any(), any(Integer.class))).thenReturn(1);
33
34		// exercise/execute
35		int sisaBarang = orderService.order("handphone", 1);
36
37		// assert
38		assertEquals(1,sisaBarang);
39	}
40}

Karena di library Mockito java, kita bisa melakukan Stub di object Mock sekaligus, maka Mock dan Stub ini bisa disetting barengan.

Apa yang terjadi ?

  • Kita melakukan mocking terhadap object OrderRepository., (line 21 dan 22)
  • Kita melakukan set-up terhadap object OrderService dan object OrderRepository., (line 26)
  • Kita melakukan manipulasi data terhadap output dari fungsi orderRepository.orderByNameAndCount(namabarang, jumlah), (line 32)
  • Kita melakukan eksekusi terhadap fungsi order(namabarang, jumlah) terhadap objek orderService, (line 35)
  • Dan terakhir kita melakukan verifikasi/assertion terhadap hasil kembalian fungsi order(namabarang, jumlah) di objek orderService. (line 38)

Yang menjadi koentji dari Stub adalah step ketiga diatas, yaitu Manipulasi Data/Context.

Kalau kita mengacu kepada konsep Test Stub di buku Gerard Meszaros yang berjudul XUnit Test Pattern, Refactoring Test Code,

maka ada 3 tahapan di dalam melakukan Stub, yaitu :

flowchart LR a0(Setup)-->a1(Exercise/eksekusi)-->a3(Verify)

Didalamnya tidak ada fungsi set expectation, tetapi lebih menekankan kepada step Verify data/context.

Jadi kesimpulannya, dengan Stub, maka kita mempunyai kesempatan untuk mengetes fungsi dengan data yang kita inginkan.


Kesimpulan.

Secara Umum kita bisa menyimpulkan bahwa :

  • Mock –> digunakan untuk melakukan Verifikasi Interaksi/Fungsi/Behaviour.
  • Stub –> digunakan untuk melakukan Verifikasi Data/Context/Kasus.

ITU ….!!!