कॉन्ट्रैक्ट टेस्टिंग
Contract testing किसी integration point की हर side को अलग से एक shared contract के विरुद्ध टेस्ट करके जाँचती है: वे requests जो consumer भेजता है, और वे responses जिनकी वह provider से उम्मीद करता है। Consumer call करता है (mobile app या कोई दूसरी service); provider जवाब देता है (API)। किसी भी test को दूसरी side चलती हुई नहीं चाहिए, इसलिए हर team अपना हिस्सा अपनी pipeline में verify करती है।
उदाहरण: mobile app, Orders API के GET /orders/{orderId} से id, status और total पढ़ता है। App को एक mock के विरुद्ध टेस्ट किया जाता है जो contract के मुताबिक जवाब देता है; Orders API को request replay करके टेस्ट किया जाता है कि उसका असली response अब भी contract में fit होता है या नहीं। अगर कोई developer total का नाम बदल दे, तो provider का check उसकी अपनी pipeline में ही fail हो जाता है, दोनों के किसी shared environment में मिलने से बहुत पहले। Contract या तो consumer-driven होता है, या OpenAPI document पर आधारित specification-based।
यह क्यों ज़रूरी है
Services और apps अलग-अलग release होते हैं। Provider का कोई change अपने सारे tests pass करके भी किसी consumer को तोड़ सकता है, क्योंकि वे tests नहीं जानते कि consumer क्या पढ़ता है। Contract tests के बिना यह break तभी दिखता है जब दोनों साथ चलें: दिनों बाद किसी end-to-end environment में, या production में, जहाँ mobile app के पुराने versions API को call करते रहते हैं।
Tester के लिए contract tests release के एक सटीक सवाल का जवाब देते हैं: क्या provider का यह version हर उस consumer version के साथ काम करेगा जिससे वह मिलेगा? जवाब "नहीं" हो, तो वे consumer, interaction और field बताते हैं।
Consumers, providers और contract
Martin Fowler contract test को ऐसा check बताते हैं जो पक्का करता है कि कोई test double, यानी वह stub या mock जिसे दूसरे tests किसी service की जगह इस्तेमाल करते हैं, अब भी असली service की तरह व्यवहार करता है। Contract testing यही विचार दोनों sides पर लागू करती है: हर side एक ही लिखे हुए contract के विरुद्ध टेस्ट होती है।
HTTP API के लिए contract का हर interaction ये बातें बताता है:
- Request: method, path, query parameters, ज़रूरी headers और body।
- Expected response: status code, headers और body fields, उनके types या patterns के साथ।
- Provider state: वह data जो interaction के मायने रखने के लिए मौजूद होना चाहिए, जैसे "order ORD-1042 मौजूद है"।
Consumer-driven contract testing का तरीका
Consumer-driven contract testing में consumers अपने tests के ज़रिए contract लिखते हैं, इसलिए provider ठीक-ठीक जानता है कि हर consumer कौन-से fields इस्तेमाल करता है, और बाकी आज़ादी से बदल सकता है। कीमत: हर consumer team ये tests लिखती और maintain करती है और provider इन्हें चलाता है, इसलिए यह एक organisation की services के लिए ठीक है, अनजान consumers वाले public API के लिए नहीं।
Pact, इस approach का एक open-source tool, पाँच steps में काम करता है (इसकी contract file नीचे दिखाई गई है):
- Consumer test: consumer का client code एक mock provider को call करता है जो test के घोषित तरीके से जवाब देता है, और test साबित करता है कि consumer उस response को सही handle करता है।
- Contract file: run हर interaction को एक pact में लिखता है: consumer की भेजी requests और response के वे fields जिनकी उसका test उम्मीद करता है, और ये सिर्फ़ वही fields होने चाहिए जिन्हें consumer सच में पढ़ता है।
- Publish: एक broker contract को consumer के version के साथ store करता है।
- Provider verification: provider की pipeline हर provider state set up करती है, हर request असली provider पर replay करती है और response की तुलना expected से करती है, जहाँ exact value मायने न रखे वहाँ type या pattern से; extra fields default रूप से allowed हैं।
- Deployment check: किसी भी side के deploy से पहले broker पक्का करता है कि यह version target environment के versions के विरुद्ध verify हो चुका है (Pact में
can-i-deploy)।
Consumer pipeline: mobile-app
Mock के विरुद्ध टेस्ट, असली Orders API के विरुद्ध कभी नहीं
- 1Consumer testClient code एक mock provider को call करता है जो घोषित जवाब देता है
- 2Contract लिखा गयासिर्फ़ भेजी गई requests और पढ़े गए fields
GET /orders/ORD-1042 → 200 {id, status, total} - 3Broker में publishConsumer version के साथ store
Provider pipeline: orders-api
App के बिना, contract के विरुद्ध verify
- 4Provider state set upInteraction के लिए ज़रूरी test data
order ORD-1042 exists and is shipped - 5Request replay, response matchअसली provider पर, type या pattern से
शाखा:
totalrename →$.totalग़ायब: verification failफ़ेल - 6Deployment check
can-i-deployसिर्फ़ एक-दूसरे के विरुद्ध verified versions release होते हैंGate
नाम Pact से हैं: contract file को pact और check को can-i-deploy कहा जाता है; दूसरे tools इन्हीं steps को अलग नाम देते हैं।
{
"consumer": { "name": "mobile-app" },
"provider": { "name": "orders-api" },
"interactions": [
{
"description": "a request for an existing order",
"providerStates": [{ "name": "order ORD-1042 exists and is shipped" }],
"request": { "method": "GET", "path": "/orders/ORD-1042" },
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": { "id": "ORD-1042", "status": "shipped", "total": 149.5 },
"matchingRules": {
"body": {
"$.id": { "matchers": [{ "match": "regex", "regex": "^ORD-[0-9]+$" }] },
"$.total": { "matchers": [{ "match": "type" }] }
}
}
}
}
],
"metadata": { "pactSpecification": { "version": "3.0.0" } }
}OpenAPI के साथ specification-based contract testing
यहाँ provider का OpenAPI document ही contract है; कुछ teams इसे provider-driven contract testing कहती हैं। API schema validation वाले lesson का check provider के tests के हर response पर चलता है, errors समेत, और consumers अपने mocks उसी document से बनाते हैं।
इसका blind spot: अगर code और document साथ बदलें, तो provider अपने document से match करता ही रहेगा। इसलिए document के हर नए version की तुलना production version से भी होती है, और breaking changes flag किए जाते हैं:
- Breaking: operation या response field हटाना या rename करना, type बदलना, request का optional field required बनाना, पहले accept होने वाली request values reject करना।
- आम तौर पर safe: नया operation, request में optional field या response में नया field जोड़ना, अगर consumers अनजान fields ignore करते हों।
- फ़ैसला चाहिए: response में नई
enumvalue याnull; जो client सिर्फ़ जानी-पहचानी values handle करता है वह fail हो सकता है। - सीमा: document बताता है कि provider क्या देता है, यह नहीं कि कौन-सा consumer क्या इस्तेमाल करता है, इसलिए "breaking" change शायद किसी पर असर न डाले।
Worked example: Orders API में एक field का नाम बदलना
Orders API के दो consumers के contracts हैं: mobile app id, status और total पढ़ता है; delivery service id, status और deliveryCity पढ़ती है। एक release total का नाम grandTotal करती है, createdAt को text date से number बनाती है और एक optional giftMessage जोड़ती है। OpenAPI document भी update हुआ, इसलिए उसके विरुद्ध validation pass होता है।
- दोनों documents की तुलना दो breaking changes flag करती है:
totalहटाया गया औरcreatedAtका type बदला। - Mobile app का provider verification
$.totalपर fail होता है; delivery service pass होती है, क्योंकि वह इनमें से कोई field इस्तेमाल नहीं करती। - कोई contract
createdAtनहीं पढ़ता, इसलिए type का बदलाव दोनों जाने-पहचाने consumers के लिए safe है, पर broker से बाहर के consumers के लिए नहीं। - Deployment check release रोक देता है। Team
totalऔरgrandTotalदोनों लौटाती है,totalकोdeprecatedmark करती है, और उसे तब हटाती है जब app के किसी supported version को उसकी ज़रूरत न हो। - End-to-end run यह rename तभी दिखाता जब दोनों services deploy हो चुकी होतीं, बिना amount वाली order screen के रूप में।
End-to-end tests की तुलना में contract tests क्या पकड़ते हैं
End-to-end (E2E) test हर असली service से होकर journey चलाता है, इसलिए वह incompatible interface तभी पकड़ता है जब सारी services एक साथ deploy हों; वह धीमा है, और उसके failure को कई interfaces से होकर trace करना पड़ता है। Contract test एक interface को सेकंडों में, हर side की अपनी pipeline में जाँचता है और consumer, interaction और field बताता है।
- Deployment से पहले पकड़ता है: हटाए गए, rename हुए या type बदले fields, बदले हुए paths, methods, status codes या headers, और वे requests जिन्हें provider अब accept नहीं करता।
- गलत values, business rules और मतलब नहीं पकड़ता: गलत calculate हुआ total, या halalas की जगह भेजे गए riyals (integration testing वाला lesson), सही type के ही होते हैं।
- Configuration और deployment नहीं पकड़ता: गलत base URL, ग़ायब credentials या expired certificate।
- जो किसी contract में नहीं, वह नहीं पकड़ता: जैसे ऐसी
enumvalue या error जो कोई provider state नहीं बनाती।
आम गलतियाँ
- Consumer जो पढ़ता है उसकी बजाय provider के पूरे response से contract लिखना, जिससे provider का हर harmless change verification fail कर देता है।
- जहाँ सिर्फ़ type मायने रखता है वहाँ exact values match करना (generated ids, timestamps), या जहाँ value मायने रखती है वहाँ सिर्फ़ type (status codes,
enumvalues)। - सिर्फ़ success responses के contracts लिखना;
404,401, validation errors और खाली lists को ही clients सबसे ज़्यादा गलत handle करते हैं। - Provider verification चलाना पर deployment को उससे gate न करना, जिससे incompatible versions फिर भी production तक पहुँचते हैं।
QA का नज़रिया
Contract का काम refinement में शुरू होता है: हर API change पर पूछें कि endpoint को कौन consume करता है, वे कौन-से fields पढ़ते हैं और क्या change breaking है। आपके टेस्ट किए APIs का consumer map इसका जवाब देता है और ग़ायब contracts दिखाता है। Contracts को test cases की तरह review करें: क्या errors, खाली lists और optional या nullable fields cover हैं, और क्या provider states realistic data इस्तेमाल करती हैं?
Pipeline में जाँचें कि contracts publish होते हैं, verification provider के हर change पर चलता है और deployment check release रोक सकता है। Verification fail हो तो दोनों versions, interaction, field path और expected व actual values record करें, फिर पूछें कि क्या change जानबूझकर था: अगर हाँ, तो coordinated rollout plan करें; अगर नहीं, तो provider में regression report करें।
अभ्यास
दो consumers के contracts के विरुद्ध चार API changes परखें
customers-api team GET /customers/{id} में चार changes plan कर रही है, जिसे loyalty-app और sms-service contracts के तहत consume करते हैं (material देखें)। तय करें कि हर approach क्या report करेगी और team को क्या करना चाहिए।
customers-api GET /customers/{id}
आज का response (200):
{ "id": "C-77", "name": "Sara Al-Harbi", "phone": "+966500000001",
"tier": "gold", "createdAt": "2026-03-01T09:30:00Z" }
Consumer contracts (हर contract कौन-से fields पढ़ता है):
loyalty-app id (string), name (string), tier ("standard" | "gold")
sms-service id (string), phone (string)
दोनों contracts में provider state: customer C-77 मौजूद है (tier "gold")
अगली release के planned changes:
1 एक optional field "email" (string) जोड़ना
2 "phone" का नाम बदलकर "mobile" करना
3 tier की नई value "platinum" की अनुमति देना
4 "createdAt" को ISO 8601 string से number (Unix seconds) बनाना- हर change को वैसे बाँटें जैसे पुराने और नए OpenAPI document की तुलना बाँटेगी: breaking, आम तौर पर safe, या फ़ैसला चाहिए।
- हर change के लिए तय करें कि किस consumer का verification fail होगा, अगर किसी का होगा।
- जहाँ दोनों जवाब अलग हैं वहाँ चिह्न लगाएँ और कारण समझाएँ।
- जो change किसी consumer को तोड़ता है, उसके लिए safe rollout सुझाएँ।
मॉडल उत्तर दिखाएँ
Change 1 (email): आम तौर पर safe; दोनों verifications pass होते हैं, क्योंकि किसी contract में इसका ज़िक्र नहीं और response में extra fields default रूप से allowed हैं। दोनों approaches यहाँ सहमत हैं।
Change 2 (phone से mobile): breaking; sms-service $.phone पर fail होता है, loyalty-app pass होता है। दोनों fields लौटाएँ, phone को deprecated mark करें, sms-service को switch करके नया contract publish करने दें, फिर phone तब हटाएँ जब sms-service के किसी deployed version को उसकी ज़रूरत न हो।
Change 3 (platinum): response में नई enum value पर फ़ैसला चाहिए, और हर verification pass होता है क्योंकि provider state एक gold customer लौटाती है। लेकिन loyalty-app सिर्फ़ standard और gold accept करता है, इसलिए पहला platinum customer उसे तोड़ सकता है, और दोनों में से कोई check यह नहीं दिखाता। App team से पूछें, loyalty-app का ऐसा version release करें जो platinum (या कोई भी unknown tier) handle करे, उसके contract में platinum customer वाली provider state जोड़ें, और उसके बाद ही API को नई value लौटाने दें। End-to-end run यह problem तभी दिखाएगा जब उसके data में कोई platinum customer हो।
Change 4 (createdAt number बनता है): document में breaking, फिर भी हर verification pass होता है, क्योंकि कोई contract इसे नहीं पढ़ता: इन दो consumers के लिए safe, broker से बाहर के किसी भी consumer के लिए breaking। अगर ऐसे consumers हो सकते हैं, तो number को एक नए field में जोड़ें और createdAt को जैसा है वैसा रहने दें।
अक्सर पूछे जाने वाले सवाल
क्या contract testing के लिए Pact ज़रूरी है?
नहीं। Pact consumer-driven contracts के लिए बना एक open-source tool है, लेकिन specification-based contract testing के लिए सिर्फ़ OpenAPI document और एक validator चाहिए। Tool से ज़्यादा principle मायने रखता है: हर side को एक shared, versioned contract के विरुद्ध टेस्ट करें, और deployment को result से gate करें।
Consumer-driven contracts कौन लिखता है, developers या testers?
आम तौर पर consumer के developers consumer tests consumer के code base में लिखते हैं, क्योंकि ये client code के tests हैं। Provider team verification और provider states की owner होती है। Tester review करता है कि कौन-से interactions cover हैं (errors, खाली lists, optional fields), और जाँचता है कि provider states realistic data इस्तेमाल करती हैं और deployment check pipeline में है।
क्या contract tests end-to-end tests की जगह ले लेते हैं?
आंशिक रूप से। वे उन end-to-end tests की जगह लेते हैं जिनका एकमात्र मकसद यह दिखाना है कि दो services अब भी आपस में बात कर पाती हैं। Critical flows, configuration और deployment, और कई services में फैले business rules के लिए कुछ end-to-end journeys रखें, क्योंकि contracts इन्हें नहीं देख सकते।
क्या contract testing third-party API के साथ काम करती है?
Consumer-driven रूप नहीं: उसके लिए provider को आपका contract चलाना पड़ता है, और payment gateway जैसा third party ऐसा नहीं करेगा। Specification-based रूप इस्तेमाल करें: उसके sandbox responses को उसके published OpenAPI document के विरुद्ध validate करें, अगर वह मौजूद हो, और नियमित रूप से sandbox पर कुछ tests चलाएँ ताकि पक्का हो कि आपके mocks अब भी असली service से मेल खाते हैं; contract test का Fowler वाला मूल अर्थ यही है।
संदर्भ
- Contract Test (Martin Fowler)Contract tests: समय-समय पर होने वाली जाँचें कि test double पर किए गए calls वही नतीजे लौटाते हैं जो असली बाहरी सेवा पर वही calls लौटाते।
- Introduction — Pact DocsPact के साथ consumer-driven contract testing: हर एप्लिकेशन को अलग से उस contract के सामने टेस्ट किया जाता है जो consumer के टेस्ट चलते समय बनता है।
- OpenAPI SpecificationREST API के वर्णन का मानक: पाथ, ऑपरेशन, पैरामीटर, स्कीमा, रिस्पॉन्स और सिक्योरिटी स्कीम।
- The Practical Test Pyramid (Ham Vocke)ऑटोमेटेड टेस्ट पोर्टफ़ोलियो में unit, integration, contract और UI टेस्ट का संतुलन कैसे रखें — व्यापक रूप से उद्धृत व्याख्या।
Mutqan tools से अभ्यास करें
जाँचें कि कोई API रिक्वेस्ट/रिस्पॉन्स जोड़ा अपने OpenAPI कॉन्ट्रैक्ट का पालन करता है या नहीं: path, method, पैरामीटर, बॉडी और रिस्पॉन्स स्कीमा।
किसी असली API रिस्पॉन्स की तुलना अपेक्षित कॉन्ट्रैक्ट (उदाहरण या स्कीमा) से करें और breaking बनाम non-breaking अंतर देखें।
OpenAPI 3.x और Swagger 2.0 दस्तावेज़ों में संरचना की ग़लतियाँ तथा best-practice समस्याएँ जाँचें।
OpenAPI 3.x / Swagger 2.0 स्पेक (JSON या YAML) को ब्राउज़ करने लायक़ endpoint दस्तावेज़ों के रूप में दिखाएँ।
दो JSON डॉक्यूमेंट की सिमैंटिक तुलना करें और जोड़ी गई, हटाई गई तथा बदली गई वैल्यू हाइलाइट करें।
और गहराई से
इस topic में आपकी प्रगति
प्रगति ट्रैक करने के लिए साइन इन करें
Topics पूर्ण चिह्नित करें, जहाँ छोड़ा था वहीं से जारी रखें और specialisation परीक्षाएँ खोलें।