SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Mocking dan Pengujian API Self-Hosted di VPS

Jalankan WireMock dan Hurl di VPS dengan satu repository: stub WireMock tersimpan di Git, suite Hurl berjalan di CI, dan laporan tetap ada setelah rebuild.

Dua pekerjaan yang menggunakan satu repository

Mocking dan pengujian API secara self-hosted adalah dua pekerjaan yang berbeda. Menganggap keduanya sebagai satu pekerjaan dapat membuang waktu hingga satu minggu. Mock server menggantikan dependency yang tidak dapat Anda panggil dari CI, seperti penyedia pembayaran, API partner, upstream dengan rate limit, atau layanan yang belum dirilis oleh tim lain. API test runner memanggil endpoint milik Anda sendiri dalam urutan tetap dan memverifikasi responsnya. Nilai dari satu respons dapat digunakan dalam request berikutnya.

Keduanya tidak tumpang tindih. Mock server tidak pernah melaporkan pass atau fail. Test runner tidak menentukan respons payment provider ketika kartu ditolak. Sebagian besar tim yang sudah menyewa server akhirnya menjalankan masing-masing satu instance. Keduanya dijalankan oleh file Docker Compose yang sama dan ditinjau dalam pull request yang sama.

Mengapa melakukan mocking dan pengujian API secara self-hosted?

Data fixture Anda memiliki bentuk seperti data produksi. Body permintaan dalam pengujian API dapat berupa data pelanggan nyata yang hanya namanya diubah, atau bahkan namanya tidak diubah karena tidak ada yang memeriksanya. Stub hasil perekaman lebih berisiko: perekaman proxy menyimpan apa pun yang sebenarnya dikembalikan oleh upstream. Akibatnya, direktori stub yang dibuat melalui perekaman dapat berisi token aktif dan alamat email pelanggan sampai seseorang membaca setiap file. Pada layanan hosted, data tersebut menjadi insiden keamanan dan pengungkapan data yang harus Anda tanggung melalui pihak lain.

Alasan kedua adalah keterjangkauan jaringan. Service yang terikat pada alamat privat tidak dapat diakses dari runner hosted, sehingga pengujian sama sekali tidak dapat dijalankan. Setiap solusi alternatif memiliki biaya. Mempublikasikan API ke Internet untuk mengujinya menghilangkan alasan API tersebut dibuat privat. Tunnel atau salinan staging publik merupakan sistem lain yang harus dipelihara, dan salinan staging dapat berbeda dari produksi di antara rilis. Runner pada jaringan privat yang sama dapat memanggil service secara langsung dan tidak memerlukan semua itu. Inilah alasan praktis di balik runner GitHub Actions self-hosted.

Server mock self-hosted mana yang sebaiknya Anda jalankan?

Masing-masing berjalan sebagai container pada server yang Anda miliki. Hal yang penting adalah menentukan sumber kebenaran yang digunakan masing-masing opsi, karena hal itu menentukan apakah membangun ulang container tidak menimbulkan biaya apa pun atau menghabiskan waktu sepanjang sore.

  • WireMock menyimpan setiap stub sebagai file JSON dalam direktori mappings/, dengan body respons berukuran besar dalam __files/. Image-nya adalah wiremock/wiremock, direktori root di dalam container adalah /home/wiremock, dan WireMock juga dapat berjalan sebagai proxy perekam. File pada disk berarti mock dapat disimpan di git seperti kode lainnya.
  • Mockoon CLI menyimpan seluruh mock API dalam satu file data JSON. Instal dengan npm install -g @mockoon/cli dan jalankan dengan mockoon-cli start --data ./data-file.json, atau jalankan image mockoon/cli dengan file tersebut di-bind mount. Aplikasi desktop mengedit file yang sama, sehingga perancangan melalui UI dan commit hasilnya tetap kompatibel.
  • MockServer berjalan dari image mockserver/mockserver dan mendengarkan pada port 1080. Expectation dikirim melalui REST API miliknya sendiri. Cara ini praktis dari kode pengujian, tetapi berisiko untuk deployment: expectation yang dibuat melalui panggilan HTTP akan hilang saat container dimulai ulang. Gunakan file inisialisasi JSON untuk stub yang harus permanen.
  • Prism membangun mock dari dokumen OpenAPI, bukan dari file stub terpisah. Instal dengan npm install -g @stoplight/prism-cli, lalu jalankan prism mock openapi.yaml. Di dalam container, tambahkan -h 0.0.0.0 karena Prism secara default bind ke localhost dan tidak dapat diakses dari luar container.
  • Microcks adalah opsi dengan cakupan terbesar: UI web yang mengimpor dokumen OpenAPI dan koleksi Postman, lalu menyajikannya sebagai mock dan menjalankan pengujian kontrak. Instalasi penuh memerlukan MongoDB dan Keycloak, serta Kafka untuk fitur asinkronnya. Image all-in-one microcks-uber menyertakan MongoDB dalam memori, yang menurut dokumentasi proyek ditujukan untuk penggunaan ephemeral. Karena itu, perlakukan semua yang dibuat di UI tersebut sebagai data sementara dan simpan artefak sumbernya di git.

Pelari pengujian API self-hosted mana yang sebaiknya digunakan?

Tugas ini berupa rangkaian langkah: melakukan autentikasi, membuat order, membacanya kembali, lalu memastikan statusnya berubah. Nilai dari satu response harus ditangkap dan digunakan dalam request berikutnya. Tool yang tidak dapat mempertahankan state di antara pemanggilan hanya berfungsi sebagai health check, bukan pengujian API.

  • Hurl menjalankan file teks biasa yang berisi request HTTP dari satu binary. Bagian [Captures] mengambil nilai dari response, bagian [Asserts] memeriksanya, dan --test mengubahnya menjadi test runner dengan ringkasan serta exit code. Version 8.0.1 merupakan versi terbaru per August 2026.
  • Bruno CLI menjalankan folder yang berisi file .bru. Instal dengan npm install -g @usebruno/cli, lalu jalankan bru run folder --env Local --reporter-junit results.xml. Format collection ini memang dirancang sebagai file teks dalam sebuah direktori, sehingga perbedaannya mudah dibaca saat review.
  • Newman menjalankan collection Postman di luar Postman: npm install -g newman, lalu newman run collection.json -r cli,junit --reporter-junit-export results.xml. Kekurangannya ada pada format. Collection berupa satu blob JSON hasil ekspor, sehingga pengeditan dilakukan di Postman dan file di git hanya berupa salinan yang dapat menjadi usang.
  • Schemathesis merupakan jenis pemeriksaan yang berbeda. Tool ini membaca schema OpenAPI dan membuat kasus yang mencoba menghasilkan response yang dinyatakan mustahil oleh schema Anda: uvx schemathesis run https://your.api/openapi.json. Tool ini menemukan crash dan pelanggaran kontrak, tetapi tidak memahami business rule Anda. Karena itu, tool ini digunakan di samping suite berskrip, bukan sebagai penggantinya.
  • Hoppscotch self-hosted merupakan opsi berbasis web dan memerlukan instance Postgres. Pahami tradeoff tersebut sebelum menginstalnya: collection disimpan dalam database, bukan di repository Anda.

Hindari satu tool berikut. Step CI masih muncul dalam berbagai daftar tool, dan format workflow YAML-nya mudah dibaca, tetapi repository tersebut terakhir menerima commit pada August 2024. Program yang berada di antara CI dan API Anda bukan tempat yang tepat untuk kode yang tidak dipelihara.

Letakkan mock server di belakang firewall

Konfigurasi berikut menjalankan WireMock sebagai pengganti payment provider. Jika format file compose masih baru bagi Anda, Docker Compose pada VPS membahas perintah lifecycle yang digunakan dalam bagian ini.

services:
  mock-payments:
    image: wiremock/wiremock:3.13.2
    command: ["--verbose"]
    volumes:
      - ./mocks/payments:/home/wiremock
    ports:
      - "127.0.0.1:8080:8080"
    restart: unless-stopped

Prefix 127.0.0.1: pada port adalah bagian yang penting. 8080:8080 tanpa prefix akan memublikasikan mock pada setiap interface, termasuk IP publik Anda. Mock tetap dapat diakses meskipun ufw menolak port tersebut karena Docker menulis aturannya sendiri ke dalam chain DOCKER iptables. Aturan itu dievaluasi sebelum aturan INPUT milik ufw. Gunakan bind ke alamat loopback atau alamat interface privat. Dengan begitu, kernel tidak pernah menerima koneksi dari luar.

Service yang diuji kemudian diarahkan ke mock. Jika service berjalan dalam project compose yang sama, URL dasar mock adalah http://mock-payments:8080 karena compose menyelesaikan nama service pada jaringannya sendiri. Jika service berjalan pada host, URL-nya adalah http://127.0.0.1:8080. Tetapkan URL tersebut melalui environment variable, bukan di dalam kode. Jika tidak, URL pengujian dapat ikut dirilis ke production.

Stub disimpan di ./mocks/payments/mappings/, satu file JSON untuk setiap stub.

{
  "request": {
    "method": "POST",
    "urlPath": "/v1/charges",
    "bodyPatterns": [{ "matchesJsonPath": "$.amount" }]
  },
  "response": {
    "status": 201,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": { "id": "ch_test_001", "status": "succeeded", "amount": 4200 }
  }
}

Jalankan service tersebut, lalu periksa data yang benar-benar dimuat.

docker compose up -d --wait mock-payments
curl -fsS http://127.0.0.1:8080/__admin/mappings

--wait menunggu sampai container melaporkan status sehat. Ini berfungsi karena image WireMock menyediakan HEALTHCHECK untuk endpoint /__admin/health. Pemanggilan mappings menampilkan semua stub yang dibaca server. Jika stub yang Anda tulis tidak ada dalam daftar tersebut, stub itu tidak pernah dimuat. Periksa apakah file berada di bawah mappings/, bukan di root yang di-mount, lalu periksa apakah JSON dapat di-parse.

Jika request masuk dan tidak ada stub yang cocok, WireMock memberikan respons 404 dengan body yang diawali Request was not matched, lalu diff terhadap stub terdekat yang dimilikinya. Baca diff tersebut sebelum mengubah apa pun karena diff itu menyebutkan field yang berbeda secara tepat. Biasanya, perbedaannya berupa path dengan /v1/charge, sedangkan stub menetapkan /v1/charges.

Tulis pengujian sebagai rangkaian dengan status yang diteruskan antar-panggilan

File Hurl berupa teks biasa. Instal paket deb dari rilis proyek.

VERSION=8.0.1
curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/$VERSION/hurl_${VERSION}_amd64.deb
sudo apt update && sudo apt install ./hurl_${VERSION}_amd64.deb

Suite yang menguji API Anda sendiri terhadap mock berada di tests/checkout.hurl.

POST {{base_url}}/orders
Content-Type: application/json
{
  "sku": "ssd-1tb",
  "amount": 4200
}
HTTP 201
[Captures]
order_id: jsonpath "$['id']"

GET {{base_url}}/orders/{{order_id}}
HTTP 200
[Asserts]
jsonpath "$.status" == "paid"
jsonpath "$.charge_id" == "ch_test_001"

Blok [Captures] yang membuatnya menjadi pengujian API, bukan dua permintaan yang tidak saling terkait. order_id dibaca dari respons pertama lalu diinterpolasikan ke dalam URL permintaan kedua. Assertion pada charge_id adalah inti seluruh pengujian ini: assertion tersebut membuktikan bahwa service Anda memanggil penyedia pembayaran dan menyimpan respons yang diterima, sedangkan nilai pembandingnya adalah nilai yang Anda tulis ke dalam stub WireMock. Kini satu file mencakup kedua bagian alur tersebut.

hurl --test --variable base_url=http://127.0.0.1:3000 \
  --report-junit reports/junit.xml \
  --report-json reports/json \
  tests/

Proses yang berhasil mencetak satu baris untuk setiap file dan sebuah ringkasan.

tests/checkout.hurl: Success (2 request(s) in 61 ms)
Executed files:    1
Executed requests: 2 (30.1/s)
Succeeded files:   1 (100.0%)
Failed files:      0 (0.0%)
Duration:          64 ms

Kegagalan mencetak error: Assert failure beserta file dan nomor baris, lalu nilai yang diperoleh dibandingkan dengan nilai yang diharapkan. Setelah itu, hurl keluar dengan kode non-zero sehingga CI berhenti. Jika status membaca pending padahal Anda mengharapkan paid, service Anda tidak memproses respons dari mock. Hal berikutnya yang perlu dibaca adalah jurnal permintaan WireMock di /__admin/requests, yang menunjukkan apakah panggilan tersebut mencapai mock sama sekali.

Picu suite dari CI runner Anda sendiri

Dengan runner yang terdaftar pada box yang sama, alurnya singkat. Runner adalah proses biasa pada host, sehingga docker dan hurl harus terpasang pada host tersebut. Runner tidak mewarisi apa pun dari image yang di-host.

name: api-tests
on: [push]
jobs:
  hurl:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - name: Start the mock
        run: docker compose up -d --wait mock-payments
      - name: Run the suite
        run: hurl --test --variable base_url=http://127.0.0.1:3000 --report-junit reports/junit.xml tests/
      - name: Archive the reports
        if: always()
        run: install -d /srv/api-tests/reports/$GITHUB_SHA && cp -r reports/. /srv/api-tests/reports/$GITHUB_SHA/
      - name: Stop the mock
        if: always()
        run: docker compose down

if: always() pada langkah pengarsipan sangat penting. Tanpanya, eksekusi pengujian yang gagal akan melewati proses penyalinan, sehingga laporan yang ingin Anda baca justru hilang. Hasil penyalinan juga harus ditempatkan di luar workspace, karena runner membersihkan workspace sebelum job berikutnya dan laporan ikut terhapus.

Simpan hasil, bukan hanya hasil dari proses terakhir

Satu file JUnit XML per commit menjawab satu pertanyaan: apakah pengujian berhasil. File tersebut tidak menjawab kapan sebuah endpoint mulai menjadi lebih lambat, karena tidak ada proses yang membaca file itu setelah Anda berhenti membukanya. Untuk melihat tren, tambahkan satu baris untuk setiap proses ke database kecil pada server yang sama. Satu tabel yang menyimpan SHA commit, nama file, jumlah pengujian yang berhasil, jumlah pengujian yang gagal, dan durasi sudah cukup. SQLite di production pada VPS adalah tempat yang wajar untuk menyimpannya: satu file, tanpa proses server, dan seluruh riwayat ikut tersimpan dalam backup yang sudah Anda buat. Parse output --report-json dari Hurl, bukan XML JUnit, karena output tersebut adalah format yang dapat dibaca mesin dari kedua format itu.

Hal yang harus tetap ada setelah container dibuat ulang

Definisi mock dan rangkaian pengujian adalah kode sumber. Keduanya harus disimpan di repository yang sama dengan service yang dideskripsikannya dan diubah dalam pull request yang sama saat sebuah endpoint diubah. Stub yang diedit melalui web UI, atau ekspektasi yang dikirim ke MockServer melalui REST API saat runtime, hanya ada di memori container tersebut atau di database tool itu. Jalankan docker compose down dan semuanya hilang. Tidak ada yang menyadarinya sampai sebuah pengujian mulai berhasil karena alasan yang keliru. Jika repository Anda juga berjalan pada hardware milik sendiri, git server yang di-host sendiri menjaga fixture dan service tetap berada dalam satu batas kepercayaan.

Berikut aturan praktisnya. Tetapkan tag image, karena latest dapat mengubah cara mock mencocokkan request tanpa perubahan apa pun di repository Anda. Kegagalan tersebut sangat sulit ditelusuri kembali ke penyebabnya. Mount direktori stub dalam mode read-only jika tool tidak perlu menulis ke dalamnya. Jangan pernah menyimpan stub mock dalam named Docker volume. Jika melakukannya, volume tersebut menjadi sumber kebenaran dan salinan di git secara diam-diam menjadi tidak sesuai.

Ada satu hal lagi yang sering terlewat. Jika Anda membuat stub dengan merekam trafik nyata melalui proxy, baca setiap file yang dihasilkan sebelum melakukan commit. Rekaman berisi persis data yang dikirim kembali oleh upstream, termasuk bearer token dan alamat email pelanggan. Melakukan commit terhadap data tersebut memasukkannya ke repository secara permanen, karena git menyimpan konten yang dihapus di dalam histori.

FAQ

Apa perbedaan antara server mock API dan runner pengujian API?

Server mock menjawab permintaan. Server ini menggantikan dependensi yang tidak dapat Anda panggil dari CI dan tidak pernah melaporkan berhasil atau gagal. Runner pengujian API mengirim permintaan ke service Anda sendiri, memverifikasi respons, meneruskan nilai dari satu pemanggilan ke pemanggilan berikutnya, dan keluar dengan status non zero jika sebuah verifikasi gagal. Keduanya menyelesaikan masalah yang berbeda. Penyiapan umumnya menjalankan keduanya sekaligus: runner memanggil service Anda, sedangkan service Anda memanggil mock.

Dapatkah saya menguji API internal dari runner CI yang di-host?

Tidak tanpa mengeksposnya. Runner yang di-host berada di luar jaringan Anda, sehingga tidak dapat menjangkau service yang terikat pada alamat privat. Pilihan Anda adalah memublikasikan API, menjalankan tunnel, atau memelihara salinan staging publik. Masing-masing menambahkan sistem yang dapat gagal atau membocorkan data. Runner pada jaringan privat yang sama dapat memanggil service secara langsung. Inilah alasan praktis utama tim memilih meng-host sendiri pekerjaan ini.

Di mana stub mock dan rangkaian pengujian API sebaiknya disimpan?

Di git, bersama service yang dijelaskannya. Tool yang menyimpan definisi sebagai file, seperti direktori mappings/ milik WireMock, file data Mockoon, file Hurl, dan folder .bru milik Bruno, menyediakan code review dan pembuatan ulang container tanpa biaya tambahan. Tool yang menyimpan definisi dalam database atau UI web memerlukan rencana pencadangan dan langkah ekspor. Ekspor adalah bagian yang sering dilupakan hingga container sudah terhapus.

Mengapa mock saya mengembalikan 404 ketika stub terlihat benar?

WireMock hanya menyajikan stub jika seluruh permintaan cocok secara persis. Permintaan yang tidak cocok mendapatkan 404 dengan body yang diawali Request was not matched, lalu diikuti perbedaan terhadap stub terdekat. Perbedaan tersebut menyebutkan field yang tidak cocok. Penyebab umum meliputi garis miring di akhir path, header Content-Type yang diwajibkan stub tetapi tidak dikirim client Anda, urlPath yang digunakan ketika stub memerlukan urlPathPattern untuk segmen variabel, serta pencocok body yang tidak sesuai dengan payload. Periksa /__admin/requests terlebih dahulu untuk memastikan permintaan memang mencapai mock.

Apakah saya masih memerlukan mock jika memiliki lingkungan staging?

Ya, karena dua alasan. Salinan staging dari upstream yang tidak Anda kendalikan tetap dapat tidak tersedia dan tetap dapat menerapkan pembatasan laju. Akibatnya, rangkaian pengujian Anda gagal karena alasan yang tidak berkaitan dengan kode Anda. Salinan tersebut juga tidak dapat menghasilkan respons yang paling perlu Anda uji, seperti kartu yang ditolak atau gateway timeout. Mock mengembalikan respons tersebut sesuai permintaan dengan kecepatan jaringan lokal. Dengan demikian, rangkaian pengujian yang memerlukan beberapa menit saat dijalankan terhadap sandbox dapat selesai dalam hitungan detik. Gunakan staging untuk pemeriksaan akhir sebelum rilis dan gunakan mock dalam CI.