E2E (end-to-end) test adalah robot yang memakai aplikasi sungguhan di simulator atau emulator, persis seperti pengguna: membuka aplikasi, mengetuk, lalu membaca layar. Bayangin uji coba sebelum cabang baru Bu Rini buka. Memeriksa mesin cuci satu per satu itu test unit. Menyuruh seorang karyawan berpura-pura jadi pelanggan, dari datang membawa cucian sampai menerima nota, itu test E2E.
🧪 Tebakan cepat: Daftar pesanan datang dari server dalam 2 detik. Test Detox-mu langsung menjalankan
await expect(element(by.id('order-13'))).toBeVisible()setelah aplikasi terbuka, tanpasleep. Lulus atau gagal? Jawabannya ada di bagian "Detox: gray-box dan sinkronisasi".
Piramida test untuk aplikasi ini
| Lapisan | Yang diuji | Alat | Jumlah |
|---|---|---|---|
| Unit | fungsi biasa: rupiah(14000) jadi 'Rp 14.000' |
Jest | banyak |
| Komponen | OrderRow menampilkan #13 dan meneruskan ketukan |
Jest + React Native Testing Library | cukup banyak |
| E2E | buka aplikasi → lihat order #13 → buka detailnya | Maestro atau Detox | sedikit, alur penting saja |
Test unit dan komponen jalan dalam hitungan detik di laptop. Test E2E butuh build aplikasi dan simulator, jadi lambat dan lebih rapuh: pakai untuk alur yang kalau rusak bikin rugi (login, lihat order, minta jemput). Versi per 11 Oktober 2026: jest-expo 57.0.5 (di atas Jest 29.7), @testing-library/react-native 14.1.0, Maestro CLI 2.11.0, Detox 20.51.4.
Test komponen: Jest dan React Native Testing Library
npx expo install jest-expo jest @types/jest --dev
npx expo install @testing-library/react-native --dev
npm install -D [email protected] # RNTL 14 butuh test-renderer, versi 1.2 untuk React 19.2
Di package.json: "jest": { "preset": "jest-expo" } dan skrip "test": "jest --watchAll". OrderRow dipindah ke src/components/OrderRow.tsx dan diberi testID:
// src/components/__tests__/OrderRow.test.tsx
import { render, screen, userEvent } from '@testing-library/react-native';
import { OrderRow } from '@/components/OrderRow';
import type { Order } from '@/data/orders';
const order13: Order = { id: '13', nama: 'Mbak Wulan', kg: 2, status: 'Disetrika' };
test('OrderRow menampilkan order #13 dan mengirim id-nya saat diketuk', async () => {
const onPress = jest.fn();
const user = userEvent.setup();
await render(<OrderRow order={order13} onPress={onPress} />);
expect(screen.getByText('#13 Mbak Wulan')).toBeOnTheScreen();
expect(screen.getByText('2 kg · Rp 14.000')).toBeOnTheScreen();
expect(screen.getByText('Disetrika')).toBeOnTheScreen();
await user.press(screen.getByTestId('order-13'));
expect(onPress).toHaveBeenCalledWith('13');
});
Dijalankan di project ini:
$ npx jest src/components/__tests__/OrderRow.test.tsx
PASS src/components/__tests__/OrderRow.test.tsx
✓ OrderRow menampilkan order #13 dan mengirim id-nya saat diketuk (460 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
- Di RNTL 14,
renderdanfireEventasync: tulisawait render(...). Contoh lama tanpaawaitdari versi 13. getByTextmenggabungkan potongan teks di satu<Text>, jadi#{order.id} {order.nama}terbaca'#13 Mbak Wulan'.userEvent.pressmenjalankan rangkaian sentuhan seperti jari sungguhan. KalautestIDsalah, hasilnyaUnable to find an element with testID: order-12, lengkap dengan pohon komponennya.
testID: pegangan untuk robot
<Pressable testID={`order-${order.id}`} onPress={() => onPress(order.id)}>
testID jadi accessibility identifier di iOS dan pengenal view di Android. Jest, Maestro (id:), dan Detox (by.id) mencari lewat situ. Mencari lewat teks juga bisa, tapi gampang patah: teksnya diterjemahkan atau diganti sedikit, test-nya gagal.
Maestro: alur dalam YAML
Maestro adalah CLI terpisah (butuh Java 17+), dipasang dengan curl -fsSL "https://get.maestro.mobile.dev" | bash. Aplikasinya gak perlu diubah sama sekali:
# .maestro/buka-order-13.yaml
appId: id.laundryburini.app
---
- launchApp:
clearState: true
- assertVisible:
id: "order-13"
- tapOn:
id: "order-13"
- assertVisible: "Mbak Wulan: Disetrika"
- Jalankan dengan
maestro test .maestro/buka-order-13.yaml, dengan simulator atau emulator yang sudah menyala dan aplikasinya terpasang (development build, ataueas builddengan"ios": { "simulator": true }dan"android": { "buildType": "apk" }). assertVisiblegak langsung gagal: Maestro mencoba lagi sampai 7 detik. Untuk yang lebih lama,extendedWaitUntil.- Di Expo Go,
launchAppdenganappIdmilikmu gak bisa dipakai. Ganti denganopenLinkke alamatexp://. - Dokumentasi Expo merekomendasikan Maestro untuk E2E di EAS Workflows.
Detox: gray-box dan sinkronisasi
Detox (dari Wix) menulis test dalam JavaScript dengan runner Jest 29, dan library-nya ikut masuk ke aplikasi. Karena "melihat ke dalam" aplikasi, Detox disebut gray-box:
// e2e/order.test.js
describe('Pesanan saya', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
it('membuka detail order #13', async () => {
await expect(element(by.id('order-13'))).toBeVisible();
await element(by.id('order-13')).tap();
await expect(element(by.text('Mbak Wulan: Disetrika'))).toBeVisible();
});
});
- Pasang:
npm install detox "jest@^29" --save-dev, lalunpx detox initmembuat.detoxrc.js,e2e/jest.config.js, dane2e/starter.test.js. Di project Expo, folder native dibuat dengan prebuild dan@config-plugins/detox(11.0.0) menyiapkannya. Tabel versi di README plugin itu baru sampai SDK 53, jadi siapkan waktu untuk mencocokkan versi. npx detox build --configuration ios.sim.debug, lalunpx detox test --configuration ios.sim.debug.- Sinkronisasi otomatis: sebelum tiap aksi dan pemeriksaan, Detox menunggu aplikasinya diam: request jaringan, timer, animasi, dan thread JavaScript. Jadi daftar yang datang dalam 2 detik ditunggu sendiri, dan
toBeVisible()lulus tanpasleep. Itu jawaban tebakannya. - Untuk yang di luar sinkronisasi (misalnya data lewat WebSocket), pakai
await waitFor(element(by.id('order-13'))).toBeVisible().withTimeout(5000). - Animasi yang berputar terus membuat aplikasi gak pernah diam, dan test menunggu sampai timeout. Matikan animasinya di build test, atau
await device.disableSynchronization()untuk bagian itu saja. - Kalau
testIDgak ketemu, Detox di iOS melaporNo elements found for “MATCHER(id == “order-13”)”.
| Maestro | Detox | |
|---|---|---|
| Bahasa test | YAML | JavaScript (Jest) |
| Menunggu | mencoba ulang sampai 7 detik per pemeriksaan | menunggu aplikasi diam (gray-box) |
| Perubahan di aplikasi | gak ada | library Detox ikut di-build, plus config plugin di Expo |
| Expo Go | lewat openLink |
gak bisa, butuh build sendiri |
Test yang kadang lulus kadang gagal (flaky)
- Data server berubah. Bu Rini memindahkan #13 ke "Siap diambil", dan
assertVisible: "Mbak Wulan: Disetrika"gagal. Pakai server staging dengan data tetap, atau API tiruan. sleeptetap. Kadang kurang, kadang buang waktu. Pakai sinkronisasi Detox,waitFor, atauextendedWaitUntil.- Dialog izin. Dialog notifikasi menutupi layar. Di iOS, Detox bisa memberi izin saat membuka aplikasi:
device.launchApp({ permissions: { notifications: 'YES' } }). - Sisa dari test sebelumnya. Login atau cache yang tertinggal. Mulai bersih dengan
clearState: true(Maestro) ataunewInstance/delete(Detox). - Menjalankan semua ini otomatis di setiap pull request dibahas di pelajaran berikutnya, CI/CD mobile.
⚠️ Jebakan yang sering ketemu
- Lupa
awaitdi RNTL 14.renderdanfireEventsekarang async. Tanpaawait, test membaca layar sebelum siap. - Gak ada
testID. Test bergantung pada teks yang bisa berubah. BeritestIDdi elemen yang diketuk dan diperiksa. - Menambah
sleepuntuk menambal test. Cari sumber tunggunya: sinkronisasi Detox,waitFor, atauextendedWaitUntil. - Semua hal diuji lewat E2E. Lambat dan rapuh. Logika di test unit, tampilan di test komponen, E2E untuk alur penting saja.
TL;DR: Piramida test Laundry Bu Rini: banyak test unit dan komponen dengan Jest (jest-expo 57.0.5, Jest 29.7) dan React Native Testing Library 14.1.0 (await render, userEvent.press, getByTestId), dan sedikit test E2E untuk alur "buka aplikasi → order #13 → detail". testID (order-13 untuk order #13) adalah pegangan untuk semua alat. Maestro (CLI 2.11.0) menulis alur dalam YAML (launchApp, assertVisible, tapOn dengan id), gak mengubah aplikasi, dan mencoba ulang tiap pemeriksaan sampai 7 detik; Expo merekomendasikannya untuk EAS Workflows. Detox (20.51.4) adalah framework E2E untuk React Native yang menjalankan test di simulator atau emulator sungguhan, ditulis dalam JavaScript, dengan sinkronisasi otomatis gray-box: dia menunggu jaringan, timer, animasi, dan thread JavaScript sendiri, dan untuk kasus khusus memakai waitFor().toBeVisible().withTimeout(). Test jadi flaky karena data yang berubah, sleep, dialog izin, dan sisa state: pakai data tetap, menunggu yang benar, dan mulai bersih.