Praktik Mandiri 10: Unit Testing dengan JUnit

1. Membuat Test Case Sederhana

Tugas: Buat class Calculator dengan method add(int a, int b) dan subtract(int a, int b). Lalu tulis test case untuk kedua method tersebut.

Contoh Code:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public int subtract(int a, int b) {
        return a - b;
    }
}

Test Case:

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class CalculatorTest {
    Calculator calculator = new Calculator();

    @Test
    void testAdd() {
        assertEquals(5, calculator.add(2, 3));
    }

    @Test
    void testSubtract() {
        assertEquals(1, calculator.subtract(4, 3));
    }
}

2. Menguji Exception

Tugas: Tambahkan method divide(int a, int b) di class Calculator yang melempar ArithmeticException jika pembagi adalah 0. Lalu tulis test case untuk memastikan exception tersebut muncul.

Contoh Code:

public int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("Pembagi tidak boleh 0");
    }
    return a / b;
}

Test Case:

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void testDivideByZero() {
    Exception exception = assertThrows(ArithmeticException.class, () -> {
        calculator.divide(5, 0);
    });
    assertEquals("Pembagi tidak boleh 0", exception.getMessage());
}

3. Menggunakan @BeforeEach dan @AfterEach

Tugas: Buat test untuk class ListManager yang mengelola list. Gunakan @BeforeEach untuk inisialisasi list dan @AfterEach untuk membersihkannya.

Contoh Code:

class ListManager {
    private List<String> items = new ArrayList<>();

    public void addItem(String item) {
        items.add(item);
    }

    public int getSize() {
        return items.size();
    }
}

Test Case:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;

class ListManagerTest {
    ListManager listManager;

    @BeforeEach
    void setup() {
        listManager = new ListManager();
        listManager.addItem("Item1");
    }

    @AfterEach
    void cleanup() {
        // Contoh: Reset list setelah setiap test
        listManager = null;
    }

    @Test
    void testListSize() {
        assertEquals(1, listManager.getSize());
    }
}

4. Mocking dengan Mockito

Tugas: Buat class PaymentService yang bergantung pada PaymentGateway. Gunakan Mockito untuk membuat mock object dari PaymentGateway.

Contoh Code:

interface PaymentGateway {
    boolean processPayment(double amount);
}

class PaymentService {
    private PaymentGateway gateway;

    public PaymentService(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    public boolean makePayment(double amount) {
        return gateway.processPayment(amount);
    }
}

Test Case:

import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
    @Mock
    PaymentGateway mockGateway;

    @InjectMocks
    PaymentService paymentService;

    @Test
    void testPaymentSuccess() {
        // Stubbing: Ketika processPayment(100.0) dipanggil, return true
        when(mockGateway.processPayment(100.0)).thenReturn(true);

        assertTrue(paymentService.makePayment(100.0));
        verify(mockGateway).processPayment(100.0); // Verifikasi pemanggilan method
    }
}

5. Parameterized Test

Tugas: Uji method isPalindrome(String str) dengan berbagai input menggunakan @ParameterizedTest.

Contoh Code:

public class StringUtils {
    public static boolean isPalindrome(String str) {
        String reversed = new StringBuilder(str).reverse().toString();
        return str.equalsIgnoreCase(reversed);
    }
}

Test Case:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@ParameterizedTest
@ValueSource(strings = {"radar", "RaceCar", "hello"})
void testPalindrome(String input) {
    boolean expected = input.equalsIgnoreCase(new StringBuilder(input).reverse().toString());
    assertEquals(expected, StringUtils.isPalindrome(input));
}

6. Menguji Performance dengan Timeout

Tugas: Pastikan method longRunningTask() selesai dalam 2 detik.

Contoh Code:

class TaskManager {
    public void longRunningTask() throws InterruptedException {
        Thread.sleep(1500); // Simulasi task yang memakan waktu 1.5 detik
    }
}

Test Case:

@Test
void testTimeout() {
    TaskManager taskManager = new TaskManager();
    assertTimeout(Duration.ofSeconds(2), () -> {
        taskManager.longRunningTask();
    });
}

7. Menguji Private Method (Menggunakan Reflection)

Tugas: Uji private method validate(String input) di class Validator.

Contoh Code:

class Validator {
    private boolean validate(String input) {
        return input != null && !input.trim().isEmpty();
    }
}

Test Case:

import java.lang.reflect.Method;

@Test
void testPrivateMethod() throws Exception {
    Validator validator = new Validator();
    Method method = Validator.class.getDeclaredMethod("validate", String.class);
    method.setAccessible(true);

    assertTrue((boolean) method.invoke(validator, "test"));
    assertFalse((boolean) method.invoke(validator, ""));
}

Tips Belajar:

  1. Pahami Annotations JUnit 5:
    • @Test@BeforeEach@AfterEach@DisplayName@Disabled
    • @ParameterizedTest@Mock@InjectMocks
  2. Gunakan Assertions yang Tepat:
    • assertEqualsassertTrueassertNullassertThrowsassertAll
  3. Best Practices:
    • Tes harus independen (tidak bergantung pada urutan eksekusi).
    • Beri nama test dengan jelas (contoh: testAdd_WhenPositiveNumbers_ReturnsSum).
    • Gunakan @DisplayName untuk deskripsi yang lebih deskriptif.
  4. Pelajari Mockito untuk Mocking:
    • when(...).thenReturn(...)verify()ArgumentMatchers
  5. Cek Coverage dengan Tools:
    • Gunakan JaCoCo atau Cobertura untuk melihat cakupan kode yang di-test.
  6. Sumber Belajar:

Semangat belajar! 😊 Coba tulis tes terlebih dahulu (TDD) sebelum implementasi kode untuk melatih pola pikir pengujian.