Test pyramid untuk arsitektur berlapis: unit test domain murni tanpa framework apa pun, test use case dengan mock port dan repository, integration test adapter terhadap infrastruktur sungguhan, contract test antar modul penjaga skema event, serta architecture tests yang memaksa dependency rule tetap dihormati — Jest dengan in-memory adapter di NestJS Fiber Go memakai testing bawaan plus testify httptest dan Laravel PHP dengan PHPUnit Pest Http fake RefreshDatabase — praktik satu use case PlaceOrder ditest tiga level tanpa database sungguhan di unit layer

Arsitektur yang kita bangun sepanjang series menyimpan bonus yang jarang diiklankan: ia sangat mudah dites — justru karena itulah hexagonal/clean dirancang. Episode ini merumuskan strategi test yang memetakan setiap level arsitektur ke jenis test paling efektifnya, sehingga suite cepat, stabil, dan menakutinya refactor hilang.
Pemetaan lapis arsitektur → jenis test:
| Lapis | Jenis Test | Infrastruktur |
|---|---|---|
| Domain (aggregate/VO) | Unit murni | Tidak ada — memori |
| Application (use case) | Unit + fake port | In-memory adapter (ep.15) |
| Infrastructure (repo/mailer) | Integration | Postgres/broker sungguhan |
| Antar modul/context | Contract test | Schema API/event |
| Struktur kode itu sendiri | Architecture test | dependency-cruiser/lint gate (ep.13–16) |
Prinsip alokasi biaya: makin ke atas piramida makin mahal dan rapuh — karena itu E2E hanya jalur checkout utama; kepastian logika dijamin lapis bawah.
Order dari ep.19 sudah membuktikannya — kasus perilaku diuji tanpa framework, durasi total ratusan milidetik.OrderPlaced dst.) diverifikasi dari dua sisi (publisher & consumer), sehingga perubahan pemecah kontrak ketahuan sebelum deploy — versi mini pendekatan Pact.describe('PlaceOrder use case', () => {
it('menyimpan order & merekam domain event', async () => {
const repo = new InMemoryOrders(); // driven port palsu
const bus = new SpyEventBus();
const uc = new PlaceOrder(repo, bus);
const res = await uc.exec({ customerId: 'c1',
items: [item('SKU-A', 2, Money.of(50000, 'IDR'))] });
expect((await repo.byId(res.orderId))!.status).toBe('placed');
expect(bus.published).toContainEqual(expect.objectContaining(
{ type: 'OrderPlaced' }));
});
});
// Nol database. Nol HTTP. Milidetik per kasus.Perhatikan gradasinya: contoh NestJS/Go adalah use case test dengan fake (tanpa DB), contoh Laravel adalah integration/API test dengan DB sungguhan — level piramida berbeda, keduanya sama-sama dibutuhkan dalam suite yang sehat.
Target outline: satu use case (PlaceOrder) ditest di 3 level tanpa DB sungguhan di unit layer.
LEVEL 1 - UNIT DOMAIN (aggregate):
add/remove item, invarian locked/kosong/cancel, pull events - memori murni
LEVEL 2 - USE CASE (fake port):
PlaceOrder(InMemoryRepo, SpyBus): happy path + pricing gagal + SKU dobel
-> assert state & event, TANPA database
LEVEL 3 - INTEGRATION/E2E:
HTTP nyata -> controller -> use case -> REPO SUNGGUHAN (Postgres)
-> assert response + row DB + event masuk outbox
Aturan piramida: hitung test tiap level - harus MENGECHIL ke atas
(mis. 12 : 6 : 2). Kalau e2e paling banyak, piramidamu terbalik.Tip
Metrik sehat untuk ditagih tiap retro: median durasi suite unit-domain harus tetap di bawah 5 detik walau jumlah test bertambah. Jika membengkak, hampir pasti ada dependensi infrastruktur yang menyelinap masuk ke level yang salah.
Rangkuman episode ini:
Episode 24 belajar dari kegagalan orang lain: Anti-Patterns & Refactoring Legacy Codebase — god object sampai distributed monolith, dan strategi strangler fig dengan characterization test. Sampai jumpa!