Assertion adalah pernyataan bahwa kondisi tertentu harus benar pada titik tertentu dalam test; begitu kondisinya salah, test gagal dengan pesan yang menjelaskan ketidaksesuaiannya. Kualitas suite uji sangat ditentukan oleh kualitas assertion-nya: assertion yang tepat menunjuk kegagalan dalam satu baris laporan, sedangkan assertion yang kabur hanya memberi sinyal “ada yang salah entah di mana”. Pada Chapter ini kita akan membedah assertion bawaan JUnit, belajar menulis pesan kegagalan yang membantu, mengenal gaya penulisan fluent dari AssertJ, lalu memahami pemeriksaan berkelompok dengan assertAll — dan membuktikan mekanismenya dengan framework assertion mini dari Java murni.
Assertion Dasar JUnit #
Tiga assertion JUnit yang paling sering dipakai adalah assertEquals, assertTrue, dan assertThrows. Seluruhnya berupa static method di class org.junit.jupiter.api.Assertions:
import static org.junit.jupiter.api.Assertions.*;
class PelangganTest {
@Test
void namaDanKotaSesuaiData() {
Pelanggan budi = cariPelanggan("PL-001");
assertEquals("Budi", budi.nama());
assertEquals("Bandung", budi.kota(), "kota pada data PL-001");
assertTrue(budi.email().endsWith("@example.com"));
}
@Test
void pelangganTidakDikenalDitolak() {
IllegalArgumentException e = assertThrows(
IllegalArgumentException.class,
() -> cariPelanggan("PL-999"));
assertEquals("pelanggan tidak ditemukan: PL-999", e.getMessage());
}
}Method assertEquals menerima dua argumen: nilai yang diharapkan di posisi pertama, nilai aktual di posisi kedua. Method assertThrows menerima class exception dan sebuah lambda; test lulus bila lambda melempar exception bertipe tersebut, dan instance exception dikembalikan sehingga pesannya bisa diperiksa lanjut. Urutan argumen yang terbalik adalah kesalahan paling umum pemula — pesan kegagalannya menjadi “diharapkan Bandung tetapi aktual Budi” yang menyesatkan saat dibaca cepat.
Pesan Kegagalan yang Membantu #
Pesan kegagalan default berisi nilai yang dibandingkan, tetapi pesan buatan kita sendiri dapat menyertakan konteks: id data, prasyarat, atau nomor tiket bug. JUnit menempatkan pesan sebagai argumen terakhir sehingga tetap terbaca sebagai penjelasan, bukan pengganggu:
assertEquals("Bandung", budi.kota(),
"kota pelanggan PL-001 berubah sejak migrasi data");Pesan yang baik menjawab pertanyaan “apa yang harusnya terjadi dan mengapa penting”. Hindari pesan yang hanya mengulang nama method — laporan sudah mencantumkan nama testnya. Manfaat pesan kustom paling terasa saat suite dijalankan otomatis di CI dan satu-satunya informasi yang dibaca engineer adalah baris kegagalan tersebut.
AssertJ dan Gaya Fluent #
AssertJ adalah pustaka assertion dengan API fluent yang membaca seperti kalimat. Rantai method-nya menawarkan pelengkapan kode yang kaya di IDE dan pesan kegagalan yang mendeskripsikan diri sendiri:
import static org.assertj.core.api.Assertions.assertThat;
class PelangganTestAssertJ {
@Test
void dataPelangganLengkap() {
Pelanggan budi = cariPelanggan("PL-001");
assertThat(budi.nama()).isEqualTo("Budi");
assertThat(budi.email())
.startsWith("budi")
.endsWith("@example.com");
assertThat(budi.kota()).isIn("Bandung", "Jakarta");
}
}Perhatikan bahwa satu baris assertThat(...).startsWith(...).endsWith(...) memeriksa dua kondisi berurutan pada objek yang sama. Saat gagal, AssertJ mencetak nilai aktual lengkap beserta bagian yang dibandingkan — informasi yang sering lebih kaya daripada assertion bawaan. AssertJ ditambahkan sebagai dependensi test terpisah dan boleh dipakai berdampingan dengan assertion JUnit dalam satu class.
Pemeriksaan Berkelompok dengan assertAll #
Assertion biasa bersifat fail-fast: eksekusi berhenti di kegagalan pertama, sehingga pemeriksaan setelahnya tidak pernah berjalan. Untuk verifikasi data yang kompleks, JUnit menyediakan assertAll yang menjalankan seluruh blok pemeriksaan dan melaporkan semua kegagalan sekaligus:
@Test
void dataPelangganUtuh() {
Pelanggan budi = cariPelanggan("PL-001");
assertAll("data pelanggan PL-001",
() -> assertEquals("Budi", budi.nama()),
() -> assertEquals("Jakarta", budi.kota()),
() -> assertEquals("[email protected]", budi.email())
);
}Mekanisme di balik layarnya sederhana: setiap lambda dijalankan satu per satu dalam blok try-catch yang menampung AssertionError, lalu seluruh kegagalan dikumpulkan dan dilaporkan bersama. Kita dapat membuktikannya dengan versi Java murni berikut:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ContohAssertLengkap {
record Pelanggan(String id, String nama, String email, String kota) {}
private static final Map<String, Pelanggan> BASIS_DATA = Map.of(
"PL-001", new Pelanggan("PL-001", "Budi", "[email protected]", "Bandung"),
"PL-002", new Pelanggan("PL-002", "Siti", "[email protected]", "Surabaya")
);
static Pelanggan cariPelanggan(String id) {
Pelanggan p = BASIS_DATA.get(id);
if (p == null) {
throw new IllegalArgumentException("pelanggan tidak ditemukan: " + id);
}
return p;
}
// ---- assertion mini ala framework ----
private static void sama(Object diharapkan, Object aktual) {
if (!diharapkan.equals(aktual)) {
throw new AssertionError("diharapkan '" + diharapkan
+ "' tetapi aktual '" + aktual + "'");
}
}
private static void benar(boolean kondisi, String pesan) {
if (!kondisi) {
throw new AssertionError(pesan);
}
}
// ---- assertAll mini: jalankan semua, kumpulkan kegagalan ----
private static void grup(String nama, Runnable... pemeriksaan) {
List<String> kegagalan = new ArrayList<>();
for (Runnable p : pemeriksaan) {
try {
p.run();
} catch (AssertionError e) {
kegagalan.add(e.getMessage());
}
}
if (kegagalan.isEmpty()) {
System.out.println(nama + ": semua pemeriksaan lulus");
} else {
System.out.println(nama + ": " + kegagalan.size() + " pemeriksaan gagal");
for (String k : kegagalan) {
System.out.println(" - " + k);
}
}
}
public static void main(String[] args) {
Pelanggan budi = cariPelanggan("PL-001");
// pemeriksaan tunggal: eksekusi berhenti di kegagalan pertama
System.out.println("-- pemeriksaan tunggal --");
try {
sama("Jakarta", budi.kota());
System.out.println("kota sesuai harapan");
} catch (AssertionError e) {
System.out.println("terhenti di: " + e.getMessage());
}
// pemeriksaan berkelompok: seluruh pemeriksaan tetap dijalankan
System.out.println("-- pemeriksaan berkelompok --");
grup("data pelanggan PL-001",
() -> sama("Budi", budi.nama()),
() -> sama("Jakarta", budi.kota()),
() -> benar(budi.email().endsWith("@example.com"),
"email harus diakhiri @example.com"),
() -> sama("[email protected]", budi.email())
);
}
}Output yang Dihasilkan:
-- pemeriksaan tunggal --
terhenti di: diharapkan 'Jakarta' tetapi aktual 'Bandung'
-- pemeriksaan berkelompok --
data pelanggan PL-001: 2 pemeriksaan gagal
- diharapkan 'Jakarta' tetapi aktual 'Bandung'
- diharapkan '[email protected]' tetapi aktual '[email protected]'Bagian pemeriksaan tunggal berhenti di kegagalan pertama, sedangkan pemeriksaan berkelompok tetap mengeksekusi empat lambda dan melaporkan dua kegagalan sekaligus. Mari kita bedah baris-per-baris bagian kuncinya:
- Baris
private static void sama(Object diharapkan, Object aktual): padananassertEquals— membandingkan denganequalsdan melemparAssertionErrorberisi kedua nilai saat tidak sama. - Baris
private static void grup(String nama, Runnable... pemeriksaan): padananassertAll— menerima judul dan sejumlah lambda pemeriksaan melalui varargs. - Baris
for (Runnable p : pemeriksaan): setiap lambda dijalankan;AssertionErroryang tertangkap tidak menghentikan perulangan melainkan ditampung keList<String> kegagalan. - Baris
if (kegagalan.isEmpty()): cabang lulus dan gagal dipisah; jumlah kegagalan dicetak sebelum daftarnya, meniru format laporanassertAllyang merangkum dulu lalu merinci. - Baris
grup("data pelanggan PL-001", () -> ..., () -> ...): empat lambda mengeksekusi pemeriksaan independen; lambda kedua dan keempat gagal secara sengaja untuk memperlihatkan bahwa kegagalan pertama tidak menutup pemeriksaan berikutnya.
Konsep serapan dari AssertJ bernama soft assertions memakai ide yang sama dengan bentuk berbeda: objek SoftAssertions mengumpulkan kegagalan dari rantai pemeriksaan, lalu satu panggilan assertAll() di akhir test memverifikasi seluruh tumpukan. Soft assertions cocok untuk verifikasi bertahap satu objek, sedangkan assertAll JUnit cocok untuk memeriksa banyak aspek independen sekaligus.
Dengan assertion yang tajam, test berikutnya yang akan kita pelajari menghemat penulisan: satu method uji yang menerima banyak kombinasi data lewat parameterized test.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ContohAssertLengkap.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ContohAssertLengkap {
record Pelanggan(String id, String nama, String email, String kota) {}
private static final Map<String, Pelanggan> BASIS_DATA = Map.of(
"PL-001", new Pelanggan("PL-001", "Budi", "[email protected]", "Bandung"),
"PL-002", new Pelanggan("PL-002", "Siti", "[email protected]", "Surabaya")
);
static Pelanggan cariPelanggan(String id) {
Pelanggan p = BASIS_DATA.get(id);
if (p == null) {
throw new IllegalArgumentException("pelanggan tidak ditemukan: " + id);
}
return p;
}
// ---- assertion mini ala framework ----
private static void sama(Object diharapkan, Object aktual) {
if (!diharapkan.equals(aktual)) {
throw new AssertionError("diharapkan '" + diharapkan
+ "' tetapi aktual '" + aktual + "'");
}
}
private static void benar(boolean kondisi, String pesan) {
if (!kondisi) {
throw new AssertionError(pesan);
}
}
// ---- assertAll mini: jalankan semua, kumpulkan kegagalan ----
private static void grup(String nama, Runnable... pemeriksaan) {
List<String> kegagalan = new ArrayList<>();
for (Runnable p : pemeriksaan) {
try {
p.run();
} catch (AssertionError e) {
kegagalan.add(e.getMessage());
}
}
if (kegagalan.isEmpty()) {
System.out.println(nama + ": semua pemeriksaan lulus");
} else {
System.out.println(nama + ": " + kegagalan.size() + " pemeriksaan gagal");
for (String k : kegagalan) {
System.out.println(" - " + k);
}
}
}
public static void main(String[] args) {
Pelanggan budi = cariPelanggan("PL-001");
// pemeriksaan tunggal: eksekusi berhenti di kegagalan pertama
System.out.println("-- pemeriksaan tunggal --");
try {
sama("Jakarta", budi.kota());
System.out.println("kota sesuai harapan");
} catch (AssertionError e) {
System.out.println("terhenti di: " + e.getMessage());
}
// pemeriksaan berkelompok: seluruh pemeriksaan tetap dijalankan
System.out.println("-- pemeriksaan berkelompok --");
grup("data pelanggan PL-001",
() -> sama("Budi", budi.nama()),
() -> sama("Jakarta", budi.kota()),
() -> benar(budi.email().endsWith("@example.com"),
"email harus diakhiri @example.com"),
() -> sama("[email protected]", budi.email())
);
}
}