VPS پر API mocking اور testing کیسے کریں؟
WireMock اور Hurl مختلف کام کرتے ہیں: WireMock stubs کو git میں رکھیں، Hurl suites کو CI میں چلائیں، اور rebuild کے بعد بھی reports محفوظ رکھیں۔
ایک ہی repository کے ساتھ دو کام
Self-hosted API mocking اور testing دو مختلف کام ہیں، اور انہیں ایک ہی کام سمجھنے سے ایک ہفتہ ضائع ہو جاتا ہے۔ mock server ایسی dependency کی جگہ کام کرتا ہے جسے آپ CI سے call نہیں کر سکتے: payment provider، partner API، rate limited upstream، یا ایسی service جسے دوسری team نے ابھی ship نہیں کیا۔ API test runner آپ کے اپنے endpoints کو مقررہ ترتیب سے call کرتا ہے اور responses کی بنیاد پر assertions چلاتا ہے۔ یہ ایک response سے حاصل ہونے والی values کو اگلی request میں منتقل کرتا ہے۔
یہ دونوں کام ایک دوسرے کا متبادل نہیں ہیں۔ mock server کبھی pass یا fail report نہیں کرتا۔ test runner کو اس بات سے کوئی سروکار نہیں ہوتا کہ card decline ہونے پر payment provider کیا response دیتا ہے۔ جن teams کے پاس پہلے سے rented server ہوتا ہے، ان میں سے زیادہ تر آخرکار ہر ایک کا ایک instance چلاتی ہیں۔ دونوں کو اسی Docker Compose file سے start کیا جاتا ہے اور اسی pull request میں review کیا جاتا ہے۔
خود میزبانی کے ذریعے API mocking اور testing کیوں کریں؟
آپ کے fixtures، production میں استعمال ہونے والے data کی ساخت رکھتے ہیں۔ API test میں request body کسی حقیقی customer record پر مشتمل ہوتی ہے، جس میں صرف نام تبدیل کیا گیا ہوتا ہے، یا نام بھی تبدیل نہیں کیا جاتا کیونکہ کسی نے جانچ نہیں کی ہوتی۔ Recorded stubs اس سے بھی زیادہ خطرناک ہیں: proxy recording وہی data محفوظ کرتی ہے جو upstream نے حقیقت میں واپس کیا ہو۔ اس لیے recording کے ذریعے بنائی گئی stub directory میں live tokens اور customer کے email addresses اس وقت تک موجود رہتے ہیں جب تک کوئی ہر file نہ پڑھ لے۔ Hosted service پر یہ data کسی اور کے incident اور آپ کے disclosure کا سبب بن جاتا ہے۔
دوسری وجہ reachability ہے۔ Private address پر bind کی گئی service، hosted runner سے reachable نہیں ہوتی، اس لیے test بالکل run نہیں ہو سکتا۔ ہر workaround کی کوئی نہ کوئی لاگت ہوتی ہے۔ API کو test کرنے کے لیے اسے internet پر publish کرنا اس کی private رکھنے کی بنیادی وجہ ختم کر دیتا ہے۔ Tunnel یا public staging copy ایک اور maintain کیے جانے والے system کا تقاضا کرتی ہے، اور staging copy releases کے درمیان production سے مختلف ہو جاتی ہے۔ اسی private network پر موجود runner service کو براہِ راست call کرتا ہے اور ان میں سے کسی چیز کی ضرورت نہیں ہوتی۔ یہی self-hosted GitHub Actions runner کے حق میں عملی دلیل ہے۔
کون سا self-hosted mock server چلانا چاہیے؟
ان میں سے ہر ایک آپ کے زیر انتظام سرور پر container کے طور پر چلتا ہے۔ اہم سوال یہ ہے کہ ہر سروس source of truth کے طور پر کس چیز کو استعمال کرتی ہے، کیونکہ اسی سے طے ہوتا ہے کہ container دوبارہ بنانے میں کوئی نقصان نہیں ہوگا یا پورا دن صرف ہو جائے گا۔
- WireMock ہر stub کو
mappings/directory میں JSON file کے طور پر رکھتا ہے، جبکہ بڑے response bodies__files/میں رکھے جاتے ہیں۔ اس کی imagewiremock/wiremockہے، container کے اندر اس کی root directory/home/wiremockہے، اور یہ recording proxy کے طور پر بھی چلتی ہے۔ Disk پر موجود files کا مطلب ہے کہ mock بھی دوسرے code کی طرح git میں رکھا جا سکتا ہے۔ - Mockoon CLI پوری mock API کو ایک JSON data file میں رکھتا ہے۔ اسے
npm install -g @mockoon/cliسے install کریں اورmockoon-cli start --data ./data-file.jsonسے شروع کریں، یا اس file کو bind mount کر کےmockoon/cliimage چلائیں۔ desktop app بھی اسی file میں ترمیم کرتی ہے، اس لیے UI میں design کرنا اور نتیجہ commit کرنا باہم compatible رہتا ہے۔ - MockServer
mockserver/mockserverimage سے چلتا ہے اور port 1080 پر listening کرتا ہے۔ Expectations اس کے اپنے REST API کے ذریعے بھیجی جاتی ہیں، جو test code سے استعمال کے لیے آسان لیکن deployment کے لیے خطرناک ہے: HTTP call سے بنائی گئی expectation container restart ہونے پر ختم ہو جاتی ہے۔ مستقل stubs کے لیے اس کی JSON initialization file استعمال کریں۔ - Prism الگ stub files کے بجائے آپ کی OpenAPI document سے mock بناتا ہے۔ اسے
npm install -g @stoplight/prism-cliسے install کریں، پھرprism mock openapi.yamlچلائیں۔ Container کے اندر-h 0.0.0.0شامل کریں، کیونکہ Prism default طور پر localhost پر bind ہوتا ہے اور ورنہ container کے باہر سے قابل رسائی نہیں ہوتا۔ - Microcks نسبتاً بڑا option ہے: یہ web UI فراہم کرتا ہے جو OpenAPI documents اور Postman collections import کرتی ہے، پھر انہیں mocks کے طور پر serve کرتی ہے اور contract tests چلاتی ہے۔ مکمل install کے لیے MongoDB اور Keycloak درکار ہیں، جبکہ async features کے لیے Kafka بھی چاہیے۔ all in one
microcks-uberimage in memory MongoDB شامل کرتی ہے، جسے project ephemeral use کے لیے موزوں قرار دیتا ہے۔ اس لیے UI میں بنائی گئی ہر چیز کو عارضی سمجھیں اور source artifacts کو git میں محفوظ رکھیں۔
آپ کو کون سا self-hosted API test runner استعمال کرنا چاہیے؟
یہ کام ایک sequence ہے: authenticate کریں، order بنائیں، اسے دوبارہ پڑھیں، اور تصدیق کریں کہ state تبدیل ہوئی ہے۔ اس کے لیے ایک response سے حاصل کی گئی value کو اگلی request میں استعمال کرنا ضروری ہے۔ جو tool calls کے درمیان state منتقل نہیں کر سکتا، وہ health check ہے، API test نہیں۔
- Hurl ایک single binary سے HTTP requests کی plain text files چلاتا ہے۔
[Captures]section response سے values حاصل کرتا ہے،[Asserts]section ان کی جانچ کرتا ہے، اور--testاسے summary اور exit code والے test runner میں تبدیل کرتا ہے۔ August 2026 تک Version 8.0.1 موجودہ version ہے۔ - Bruno CLI
.brufiles کے folder کو چلاتا ہے۔npm install -g @usebruno/cliسے install کریں، پھرbru run folder --env Local --reporter-junit results.xmlچلائیں۔ Collection format ڈیزائن کے لحاظ سے directory میں موجود text files پر مشتمل ہوتا ہے، اس لیے review کے دوران diffs پڑھنا آسان رہتا ہے۔ - Newman Postman کے باہر Postman collections چلاتا ہے:
npm install -g newman، پھرnewman run collection.json -r cli,junit --reporter-junit-export results.xml۔ مسئلہ format کا ہے۔ Collection ایک exported JSON blob ہوتی ہے، اس لیے editing Postman میں ہوتی ہے اور git میں موجود file ایک copy ہوتی ہے جو وقت کے ساتھ stale ہو جاتی ہے۔ - Schemathesis ایک مختلف قسم کی check ہے۔ یہ OpenAPI schema پڑھتا ہے اور ایسے cases generate کرتا ہے جو ایسی responses پیدا کرنے کی کوشش کرتے ہیں جنہیں schema ناممکن قرار دیتا ہے:
uvx schemathesis run https://your.api/openapi.json۔ یہ crashes اور contract violations تلاش کرتا ہے، لیکن آپ کے business rules سے واقف نہیں ہوتا۔ اس لیے یہ scripted suite کے ساتھ استعمال ہوتا ہے، اس کی جگہ نہیں لیتا۔ - Hoppscotch self hosted web UI کا option ہے، اور اس کے لیے Postgres instance درکار ہوتا ہے۔ اسے install کرنے سے پہلے اس tradeoff کو سمجھیں: collections آپ کی repository میں نہیں بلکہ database میں محفوظ ہوتی ہیں۔
ایک tool سے گریز کریں۔ Step CI اب بھی tool roundups میں دکھائی دیتا ہے اور اس کا YAML workflow format پڑھنے میں اچھا لگتا ہے، لیکن repository کو آخری commit August 2024 میں ملا تھا۔ آپ کے CI اور API کے درمیان چلنے والے program کے لیے unmaintained code ایک ناموزوں انتخاب ہے۔
فائر وال کے پیچھے mock server رکھیں
نیچے دیا گیا setup WireMock کو payment provider کے متبادل کے طور پر چلاتا ہے۔ اگر compose file format آپ کے لیے نیا ہے تو VPS پر Docker Compose میں وہ lifecycle commands بیان کی گئی ہیں جنہیں یہ section فرض کرتا ہے۔
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-stoppedport پر موجود 127.0.0.1: prefix اہم ہے۔ سادہ 8080:8080 mock کو ہر interface پر، بشمول آپ کے public IP کے، publish کرتا ہے۔ یہ اس وقت بھی قابل رسائی رہتا ہے جب ufw اس port کو deny کر رہا ہو، کیونکہ Docker اپنے rules DOCKER iptables chain میں لکھتا ہے اور ان کا جائزہ ufw کے INPUT rules سے پہلے لیا جاتا ہے۔ اس کے بجائے loopback address یا private interface address سے bind کریں۔ اس طرح kernel باہر سے آنے والا connection کبھی accept نہیں کرتا۔
اس کے بعد زیرِ آزمائش service کو mock کا URL دیں۔ جب service اسی compose project میں چل رہی ہو تو mock کا base URL http://mock-payments:8080 ہوتا ہے، کیونکہ compose اپنے network پر service names resolve کرتا ہے۔ جب service host پر چل رہی ہو تو یہ http://127.0.0.1:8080 ہوتا ہے۔ اسے environment variable کے ذریعے set کریں، code میں کبھی شامل نہ کریں، ورنہ test URL production میں release ہو جائے گا۔
Stubs ./mocks/payments/mappings/ میں رکھیں، ہر JSON file کے لیے ایک 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 }
}
}اسے start کریں، پھر دیکھیں کہ حقیقت میں کیا load ہوا ہے۔
docker compose up -d --wait mock-payments
curl -fsS http://127.0.0.1:8080/__admin/mappings--wait اس وقت تک انتظار کرتا ہے جب تک container healthy رپورٹ نہیں کرتا۔ یہ اس لیے کام کرتا ہے کہ WireMock image اپنے /__admin/health endpoint کے لیے HEALTHCHECK فراہم کرتی ہے۔ mappings call ان تمام stubs کی فہرست دکھاتی ہے جنہیں server نے read کیا ہے۔ اگر آپ کا بنایا ہوا stub اس فہرست میں موجود نہیں ہے تو وہ load نہیں ہوا۔ چیک کریں کہ file mounted root کے بجائے mappings/ کے اندر موجود ہے، اور یہ بھی چیک کریں کہ JSON parse ہو رہا ہے۔
جب کوئی request آئے اور اس سے کوئی stub match نہ کرے تو WireMock 404 کے ساتھ جواب دیتا ہے۔ اس کے body کا آغاز Request was not matched سے ہوتا ہے، جس کے بعد server کے پاس موجود قریب ترین stub کے ساتھ diff دیا جاتا ہے۔ کچھ بھی تبدیل کرنے سے پہلے یہ diff پڑھیں، کیونکہ اس میں وہ exact field درج ہوتی ہے جو مختلف ہے۔ عموماً یہ /v1/charge کے ساتھ ایک path ہوتا ہے، جبکہ stub میں /v1/charges درج ہوتا ہے۔
کالز کی ترتیب کے طور پر ٹیسٹ لکھیں اور کالز کے درمیان state برقرار رکھیں
Hurl فائلیں plain text ہوتی ہیں۔ پروجیکٹ کی releases سے deb انسٹال کریں۔
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آپ کی اپنی API کو mock کے خلاف جانچنے والا suite 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"[Captures] block اس ٹیسٹ کو دو غیر متعلقہ requests کے بجائے API test بناتا ہے۔ order_id کو پہلی response سے پڑھا جاتا ہے اور دوسری response کے URL میں شامل کیا جاتا ہے۔ charge_id پر assertion پوری مشق کا مقصد ہے: یہ ثابت کرتا ہے کہ آپ کی service نے payment provider کو call کیا اور واپس آنے والی value محفوظ کی۔ جس value سے موازنہ کیا جاتا ہے، وہی value ہے جو آپ نے WireMock stub میں لکھی تھی۔ اب ایک ہی فائل flow کے دونوں حصوں کا احاطہ کرتی ہے۔
hurl --test --variable base_url=http://127.0.0.1:3000 \
--report-junit reports/junit.xml \
--report-json reports/json \
tests/کامیاب run ہر فائل کے لیے ایک line اور آخر میں summary دکھاتا ہے۔
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ناکام run error: Assert failure کے ساتھ فائل اور line number دکھاتا ہے، پھر حاصل شدہ value کو متوقع value کے مقابل رکھتا ہے، اور hurl non zero کے ساتھ exit کرتا ہے تاکہ CI رک جائے۔ اگر status میں pending پڑھا جائے جبکہ آپ paid کی توقع کر رہے تھے، تو آپ کی service نے mock کی response process نہیں کی۔ اس کے بعد /__admin/requests پر WireMock request journal دیکھیں۔ اس سے معلوم ہوگا کہ call mock تک پہنچی بھی تھی یا نہیں۔
اپنے CI runner سے suite چلائیں
اسی host پر registered runner کے ساتھ workflow مختصر رہتا ہے۔ runner host پر چلنے والا ایک عام process ہے، اس لیے docker اور hurl اسی host پر installed ہونے چاہییں۔ hosted image سے کچھ بھی inherited نہیں ہوتا۔
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 downarchive step میں if: always() اہم ہے۔ اس کے بغیر failed test run میں copy step skip ہو جاتا ہے، اور آپ وہی report کھو دیتے ہیں جسے پڑھنا مقصود تھا۔ copy کو workspace سے باہر بھی محفوظ کرنا ضروری ہے، کیونکہ runner اگلی job سے پہلے workspace صاف کر دیتا ہے اور reports بھی اس کے ساتھ حذف ہو جاتی ہیں۔
نتائج محفوظ رکھیں، صرف آخری run نہیں
ہر commit کے لیے ایک JUnit XML file ایک سوال کا جواب دیتی ہے: کیا یہ کامیاب ہوئی؟ لیکن یہ نہیں بتاتی کہ کسی endpoint کی رفتار کب کم ہونا شروع ہوئی، کیونکہ آپ files کھولنا بند کر دیں تو انہیں پڑھنے والا کوئی موجود نہیں رہتا۔ Trend کے لیے ہر run کے بعد اسی box پر موجود ایک چھوٹے database میں ایک row شامل کریں۔ ایک ہی table کافی ہے جس میں commit SHA، file name، pass count، fail count اور duration محفوظ ہوں۔ اسے رکھنے کے لیے VPS پر production میں SQLite ایک مناسب جگہ ہے: ایک file، کسی server process کی ضرورت نہیں، اور پوری history اسی backup کے ساتھ محفوظ ہو جاتی ہے جو آپ پہلے ہی لیتے ہیں۔ JUnit XML کے بجائے Hurl کا --report-json output parse کریں، کیونکہ دونوں میں یہی machine-readable format ہے۔
کنٹینر rebuild کے بعد کیا محفوظ رہنا چاہیے
Mock definitions اور test suites source code ہوتے ہیں۔ انہیں اس service کے ساتھ repository میں رکھیں جس کی وہ وضاحت کرتے ہیں، اور endpoint تبدیل کرنے والی اسی pull request میں انہیں بھی تبدیل کریں۔ web UI میں edit کیا گیا stub، یا runtime پر MockServer کے REST API کے ذریعے بھیجی گئی expectation، صرف اسی container کی memory یا اس tool کے database میں موجود رہتی ہے۔ docker compose down چلانے سے یہ ختم ہو جاتی ہے، اور کسی کو اس وقت تک معلوم نہیں ہوتا جب تک کوئی test غلط وجہ سے pass ہونا شروع نہ کر دے۔ اگر آپ کی repositories آپ کے اپنے hardware پر بھی چلتی ہیں تو self-hosted git server fixtures اور service کو ایک ہی trust boundary میں رکھتا ہے۔
اب عملی اصول۔ Image tags کو pin کریں، کیونکہ latest آپ کی repository میں کسی تبدیلی کے بغیر یہ بدل سکتا ہے کہ آپ کا mock requests سے کیسے match کرتا ہے، اور ایسی failure کو اس کی اصل وجہ سے جوڑنا بہت مشکل ہوتا ہے۔ جب tool کو stub directories میں لکھنے کی ضرورت نہ ہو تو انہیں read only mount کریں۔ Mock کے stubs کو named Docker volume میں کبھی نہ رکھیں، کیونکہ پھر volume source of truth بن جاتا ہے اور git میں موجود copy خاموشی سے غلط ہو جاتی ہے۔
ایک اور بات اہم ہے، اور یہ اکثر لوگوں کو مشکل میں ڈالتی ہے۔ اگر آپ proxy کے ذریعے حقیقی traffic record کر کے stubs بناتے ہیں تو commit کرنے سے پہلے ہر generated file پڑھیں۔ Recording میں upstream کی بھیجی ہوئی ہر چیز بعینہٖ شامل ہوتی ہے، جن میں bearer tokens اور customer email addresses بھی شامل ہیں۔ اسے commit کرنے سے یہ مواد آپ کی repository میں مستقل طور پر شامل ہو جاتا ہے، کیونکہ git حذف کیے گئے content کو history میں محفوظ رکھتا ہے۔
FAQ
API mock server اور API test runner میں کیا فرق ہے؟
Mock server درخواستوں کا جواب دیتا ہے۔ یہ ایسی dependency کی جگہ استعمال ہوتا ہے جسے آپ CI سے call نہیں کر سکتے، اور یہ کبھی pass یا fail رپورٹ نہیں کرتا۔ API test runner آپ کی اپنی service کو درخواستیں بھیجتا ہے، responses پر assertions لگاتا ہے، ایک call کی values اگلی call میں منتقل کرتا ہے، اور assertion fail ہونے پر non zero حالت کے ساتھ exit کرتا ہے۔ دونوں مختلف مسائل حل کرتے ہیں۔ عام setup میں دونوں بیک وقت چلتے ہیں: runner آپ کی service کو call کرتا ہے، جبکہ آپ کی service mock کو call کرتی ہے۔
کیا میں hosted CI runner سے internal API کی testing کر سکتا ہوں؟
اسے expose کیے بغیر نہیں۔ Hosted runner آپ کے network سے باہر ہوتا ہے، اس لیے وہ private address پر bind کی گئی service تک نہیں پہنچ سکتا۔ آپ کے اختیارات API کو publish کرنا، tunnel چلانا، یا public staging copy برقرار رکھنا ہیں۔ ہر اختیار ایک ایسا system شامل کرتا ہے جو fail یا leak ہو سکتا ہے۔ اسی private network پر موجود runner service کو براہ راست call کرتا ہے۔ یہی وجہ ہے کہ teams عموماً اس کام کو self host کرتی ہیں۔
Mock stubs اور API test suites کہاں رکھنی چاہییں؟
Git میں، اس service کے ساتھ جسے وہ describe کرتی ہیں۔ وہ tools جو definitions کو files کے طور پر محفوظ کرتے ہیں، مثلاً WireMock کی mappings/ directory، Mockoon کی data file، Hurl files اور Bruno کا .bru folder، code review اور بلا معاوضہ container rebuild فراہم کرتے ہیں۔ جو tools definitions کو database یا web UI میں محفوظ کرتے ہیں، ان کے لیے backup plan اور export step درکار ہوتا ہے۔ Export وہ حصہ ہے جسے لوگ اس وقت تک بھولے رہتے ہیں جب تک container پہلے ہی ختم نہ ہو جائے۔
Stub درست نظر آنے کے باوجود میرا mock 404 کیوں واپس کرتا ہے؟
WireMock کسی stub کو صرف exact match کی صورت میں serve کرتا ہے۔ Unmatched request کو 404 ملتا ہے، جس کے body کا آغاز Request was not matched سے ہوتا ہے۔ اس کے بعد closest stub کے مقابل diff دیا جاتا ہے، اور یہ diff اس field کا نام بتاتا ہے جو مختلف ہے۔ عام وجوہات میں path کے آخر میں trailing slash، Content-Type header شامل ہیں جسے stub لازمی قرار دیتا ہے لیکن آپ کے client نے نہیں بھیجا، variable segment کے لیے urlPath استعمال کرنا جبکہ stub کو urlPathPattern درکار ہو، اور ایسا body matcher شامل ہیں جو payload سے مطابقت نہیں رکھتا۔ پہلے /__admin/requests چیک کریں تاکہ تصدیق ہو جائے کہ request mock تک پہنچی بھی تھی۔
اگر میرے پاس staging environment ہو تو کیا مجھے اب بھی mocks درکار ہیں؟
ہاں، دو وجوہات کی بنا پر۔ جس upstream پر آپ کا اختیار نہیں، اس کی staging copy بھی down ہو سکتی ہے اور rate limit بھی لگا سکتی ہے۔ اس لیے آپ کی suite ایسی وجوہات سے fail ہو سکتی ہے جن کا آپ کے code سے کوئی تعلق نہیں۔ یہ ان responses کو بھی پیدا نہیں کر سکتی جن کی testing آپ کو سب سے زیادہ درکار ہوتی ہے، مثلاً declined card یا gateway timeout۔ Mock یہ responses ضرورت کے وقت local network speed پر واپس کرتا ہے۔ اس سے sandbox کے خلاف کئی منٹ لینے والی suite چند seconds میں مکمل ہو جاتی ہے۔ Release سے پہلے آخری check کے لیے staging رکھیں اور CI میں mocks استعمال کریں۔