Menerapkan load testing pada API HTTP sungguhan: header autentikasi dan bearer token, cookies, parameterisasi request dengan data dinamis dan payload JSON, serta gabungan assertion fungsional dan performa dengan check() dan r.json().

Di episode 3 kalian telah menulis skrip k6 pertama: struktur dasar, HTTP GET dan POST, check(), group(), dan membaca output k6 run script.js. Sekarang kita bergerak dari skrip yang berjalan ke skrip yang realistis. Episode 4 fokus pada satu domain: HTTP API load testing dasar — yang berarti berurusan dengan hal-hal yang selalu muncul di API produksi: autentikasi, cookies, data dinamis, dan payload JSON.
Kenapa episode ini penting? Karena skrip yang mengirim GET / tanpa header hanyalah latihan. API sungguhan memerlukan token, mengirim data, dan memberi respons terstruktur yang harus divalidasi. Episode ini mengajarkan tiga kemampuan inti: (1) mengatur header autentikasi seperti bearer token, (2) memparameterisasi request dengan data dinamis, dan (3) menggabungkan assertion fungsional dan performa dalam satu skrip yang bisa diandalkan.
Sebelum masuk skenario, kenali tiga bentuk panggilan yang akan dominan dalam karir k6 kalian:
import http from "k6/http";
http.get("https://api.example.com/users");
http.post("https://api.example.com/login", body, params);
http.request("PUT", "https://api.example.com/users/42", body, params);Ketiganya menerima params sebagai argumen terakhir — objek yang bisa berisi headers, params, cookies, tags, dan timeout. Pola ini konsisten: URL dulu, body (jika ada) kedua, params terakhir — begitu paham, menambah method baru tinggal menyalin bentuk yang sama.
API modern mengautentikasi request lewat header Authorization dengan skema Bearer token — token (biasanya JWT) yang membuktikan identitas pengirim. Header dikirim lewat objek headers di params:
import http from "k6/http";
import { check } from "k6";
const params = {
headers: {
Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhcm1hbiJ9.secret",
Accept: "application/json",
},
};
export default function () {
const res = http.get("https://api.example.com/profile", params);
check(res, { "profile 200": (r) => r.status === 200 });
}Biasakan mendefinisikan params di luar fungsi default (fase init) jika header-nya statis — objek tidak dibuat ulang setiap iterasi. Jika token berubah (misalnya hasil login), tempatkan di setup() seperti pola di episode 2, atau buat ulang per skenario (episode 12 akan membedah OAuth2 dan JWT secara mendalam).
Untuk token yang disimpan sebagai variabel dan disisipkan ke header, gunakan template literal:
import http from "k6/http";
const token = "eyJhbGciOiJIUzI1NiJ9.payload.signature";
export default function () {
const res = http.get("https://api.example.com/users/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
}Banyak aplikasi web melacak sesi lewat cookie alih-alih header. k6 mengelola cookie secara otomatis melalui cookie jar per VU: cookie yang diterima dari respons (Set-Cookie) otomatis dikirim kembali pada request berikutnya ke domain yang sama — jadi alur "login lalu akses halaman" bekerja tanpa kode tambahan.
Jika kalian perlu mengirim cookie secara eksplisit, gunakan properti cookies di params:
import http from "k6/http";
export default function () {
const res = http.get("https://api.example.com/dashboard", {
cookies: {
session_id: "abc123",
},
});
}Poin penting yang sering mengecoh: cookie jar bersifat per VU dan tidak di-share antar VU — setiap VU punya sesi sendiri, persis pengguna browser yang berbeda. Simulasi pun lebih akurat, dan ini menjadi fondasi bab session handling di episode 6. Karena itu, pastikan skrip login berjalan di dalam fungsi default: cookie yang di-set pada iterasi pertama akan dipakai ulang pada iterasi berikutnya di VU yang sama.
Request yang setiap iterasi mengirim data yang sama adalah simulasi yang tidak realistis — pengguna sungguhan tidak login dengan username yang sama ribuan kali. Parameterisasi adalah teknik mengganti nilai statis dengan nilai yang berubah, yang umumnya diambil dari array data. Contoh paling dasar, data dari dalam skrip:
import http from "k6/http";
import { check } from "k6";
const users = [
{ username: "arif", email: "arif@example.com" },
{ username: "dewi", email: "dewi@example.com" },
{ username: "bima", email: "bima@example.com" },
];
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
const payload = JSON.stringify({
username: user.username,
email: user.email,
});
const res = http.post("https://api.example.com/register", payload, {
headers: { "Content-Type": "application/json" },
});
check(res, {
"register 201": (r) => r.status === 201,
"username echoed": (r) => r.json().username === user.username,
});
}Perhatikan pola users[Math.floor(Math.random() * users.length)] — mengambil satu elemen secara acak dari array di setiap iterasi. Untuk load test skala besar, data dari file CSV atau JSON dibaca di fase init dan didistribusikan ke VU — pola lengkapnya akan dibahas di episode 9 (data-driven testing).
Saat mengirim data ke API, dua hal harus selalu selaras: body dan header Content-Type. Body JSON dihasilkan dengan JSON.stringify dari objek JavaScript; Content-Type: application/json memberi tahu server bahwa body adalah JSON, bukan form-urlencoded.
import http from "k6/http";
import { check } from "k6";
export default function () {
const payload = JSON.stringify({
title: "Belajar k6",
author: "Arman Dwi Pangestu",
tags: ["devops", "testing"],
});
const res = http.post("https://api.example.com/posts", payload, {
headers: { "Content-Type": "application/json" },
});
check(res, {
"post created": (r) => r.status === 201,
"title echoed": (r) => r.json().title === "Belajar k6",
"id returned": (r) => r.json().id !== undefined,
});
}r.json() mem-parse body respons menjadi objek JavaScript, sehingga field bertingkat pun bisa diakses langsung — misalnya r.json().customer.name. Kesalahan klasik: lupa header Content-Type, sehingga server menerima body sebagai application/x-www-form-urlencoded dan parsing JSON gagal — gejalanya status 400 atau 422 padahal body sudah benar. Selalu sejajarkan body dan content-type.
Sekarang rangkai semuanya menjadi satu skenario utuh yang merepresentasikan HTTP API load testing yang profesional: validasi fungsional (apakah respons benar) DAN validasi performa (apakah respons cukup cepat) dalam satu pengujian. Perhatikan bahwa assertion performa ditulis sebagai check — karena itu, ia tercatat sebagai bagian dari hasil, bukan menghentikan eksekusi.
import http from "k6/http";
import { check } from "k6";
export const options = {
vus: 10,
duration: "30s",
thresholds: {
http_req_duration: ["p(95)<500"],
},
};
export default function () {
const payload = JSON.stringify({ email: "user@example.com" });
const res = http.post("https://api.example.com/login", payload, {
headers: { "Content-Type": "application/json" },
});
check(res, {
"login status 200": (r) => r.status === 200,
"token returned": (r) => typeof r.json().token === "string",
"response under 800ms": (r) => r.timings.duration < 800,
});
}Skrip ini memuat 10 VU selama 30 detik. Untuk setiap login: ia memvalidasi status HTTP, keberadaan token di respons, dan durasi request di bawah 800 milidetik. Sementara itu, threshold menegakkan aturan agregat — 95% dari seluruh request harus selesai di bawah 500 milidetik — dan jika dilanggar, k6 keluar dengan exit code 99. Inilah dua lapis kendali mutu: per-request (check) dan agregat (threshold).
Important
Pahami pembagian tugas: check memvalidasi kebenaran tiap respons, threshold menegakkan standar performa agregat. Menggunakan threshold hanya untuk "status 200" adalah penyalahgunaan — threshold mengukur metrik waktu dan rasio, check menilai isi respons. Rancang keduanya dengan peran yang jelas sejak awal.
Content-Type: application/json. Server menerima body sebagai form-urlencoded dan menolak dengan 400/422. Selalu sejajarkan.r.body.includes("ok") mudah kena false positive. Lebih andal memakai r.json().field dengan struktur yang jelas.Di episode 4 ini kalian telah menguasai HTTP API load testing dasar:
headers di params, pola Authorization: Bearer ..., dan penyisipan token lewat template literal.JSON.stringify, header Content-Type, dan membaca respons bertingkat dengan r.json().check() untuk assertion per-request (fungsional dan performa), thresholds untuk standar agregat dengan exit code 99.Kalian kini menulis load test API yang realistis dan terstruktur. Di episode 5 kita mengeksplorasi dynamic data & cookies & session handling lebih dalam: membuat data dinamis yang bervariasi per iterasi, mengelola cookie jar secara eksplisit, dan menangani skenario yang membutuhkan sesi lintas request. Sampai jumpa di episode 5!