Use Cases (अपडेट: 2/6/2026)

Claude Code से API Testing Automation: Practical Guide

Claude Code से API testing सीखें: smoke tests, auth, JSON shape, contract tests, CI और runnable examples.

Claude Code से API Testing Automation: Practical Guide

API testing का मतलब है कि browser screen खोलने से पहले server के promise को check करना। Test सीधे HTTP request भेजता है और देखता है कि login काम कर रहा है या नहीं, order बन रहा है या नहीं, error response समझने योग्य है या नहीं, authentication सच में लागू है या नहीं, और JSON shape client की expectation से मेल खाता है या नहीं।

Claude Code को अगर केवल “API tests लिख दो” कहा जाए, तो अक्सर output बहुत पतला होता है: एक happy path, एक 200 OK assertion, और बस। Real product को बचाने के लिए smoke tests, status codes, JSON shape, auth, negative tests, contract tests, test data और CI सभी चाहिए।

यह guide beginner-friendly है, लेकिन production सोच के साथ लिखी गई है। API design पर काम कर रहे हैं तो API design guide, breaking change से बचना है तो API versioning strategy, और failure investigation चाहिए तो error diagnosis workflow साथ पढ़ें।

Official docs को reference में रखना अच्छा रहता है। Playwright project में Playwright API testing देखें। नीचे का example Node.js fetch पर है, जिसका basis MDN Fetch API है। API contract के लिए OpenAPI Specification उपयोगी है।

API Test क्या साबित करे

API test का लक्ष्य बहुत सारे tests बनाना नहीं है। लक्ष्य है जल्दी जानना कि public contract टूट गया है या नहीं। Browser E2E tests जरूरी हैं, लेकिन fail होने पर root cause UI, network, session, backend, data या external provider कुछ भी हो सकता है। API test UI को skip करके server boundary को सीधे verify करता है।

Checkआसान अर्थExample
Smoke testservice minimum level पर alive है/health 204, login 200
Status codeHTTP number से resultcreate 201, no auth 401, missing 404
JSON shaperequired keys और forbidden fieldssessionId है, password वापस नहीं आता
Authenticationcaller की identity checkBearer token, cookie, API key
Negative testजानबूझकर गलत input भेजनाwrong password, empty order, unsigned webhook
Contract testpublic promise और implementation matchOpenAPI required fields response में हैं
Test dataहर run predictable state से शुरूlocal mock, reset DB, disposable order ID

सबसे बड़ा trap है केवल 200 OK देखना। Response 200 हो सकता है, फिर भी field गायब हो सकती है, error format बदल सकता है, secret leak हो सकता है, या बिना auth request pass हो सकता है। Claude Code prompt में ये expectations साफ लिखना जरूरी है।

4 Practical Use Cases

इस article का sample चार common API flows को एक छोटे test suite में रखता है।

Use caseक्यों importantक्या assert करें
Login/session smoke testज्यादातर features valid session पर depend करते हैं200, sessionId, user shape, no password
Order creation APIrevenue, inventory, receipt और support से जुड़ा है201, Location header, total, detail fetch
Webhook endpointexternal services async call और retry करते हैंno signature 401, valid event 202, duplicate safe
Regression test for bugfixed bug वापस नहीं आना चाहिए400, 401, 404, stable error JSON

Claude Code को task देते समय इन flows को नाम से लिखें। Webhook के लिए केवल success path काफी नहीं है। Missing signature, duplicate event ID और unknown order ID को जरूर cover करें।

flowchart LR
  A["OpenAPI or API notes"] --> B["Claude Code prompt"]
  B --> C["Local API test"]
  C --> D["Negative tests"]
  D --> E["CI gate"]
  E --> F["Regression safety"]

Runnable Node fetch Example

यह file production service या real database को touch नहीं करती। यह छोटा local HTTP server start करती है और Node.js built-in fetch से login, order creation, webhook और negative cases test करती है। File को api-smoke.test.mjs नाम दें और Node.js 18 या newer से चलाएं।

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import { createServer } from "node:http";

const TEST_USER = {
  id: "user_1",
  email: "demo@example.com",
  password: "correct-horse",
};
const WEBHOOK_SECRET = "whsec_test";

function sendJson(res, status, body, headers = {}) {
  if (status === 204) {
    res.writeHead(status, headers);
    res.end();
    return;
  }

  res.writeHead(status, {
    "content-type": "application/json; charset=utf-8",
    ...headers,
  });
  res.end(JSON.stringify(body));
}

function readJson(req) {
  return new Promise((resolve, reject) => {
    let raw = "";

    req.on("data", (chunk) => {
      raw += chunk;
      if (raw.length > 1_000_000) req.destroy();
    });

    req.on("end", () => {
      if (!raw) {
        resolve({});
        return;
      }

      try {
        resolve(JSON.parse(raw));
      } catch (error) {
        reject(error);
      }
    });

    req.on("error", reject);
  });
}

function bearerToken(req) {
  const value = req.headers.authorization;
  if (typeof value === "string" && value.startsWith("Bearer ")) {
    return value.slice("Bearer ".length);
  }
  return "";
}

function validateItems(items) {
  if (!Array.isArray(items) || items.length === 0) {
    return ["items must be a non-empty array"];
  }

  return items.flatMap((item, index) => {
    const errors = [];
    if (typeof item.sku !== "string" || item.sku.length === 0) {
      errors.push(`items[${index}].sku must be a non-empty string`);
    }
    if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
      errors.push(`items[${index}].quantity must be a positive integer`);
    }
    if (!Number.isInteger(item.priceCents) || item.priceCents < 0) {
      errors.push(`items[${index}].priceCents must be a non-negative integer`);
    }
    return errors;
  });
}

function makeApp() {
  const sessions = new Map();
  const orders = new Map();
  const webhookEvents = new Set();
  let orderSeq = 1;

  return async function handler(req, res) {
    const method = req.method ?? "GET";
    const url = new URL(req.url ?? "/", "http://localhost");
    let body = {};

    if (["POST", "PUT", "PATCH"].includes(method)) {
      try {
        body = await readJson(req);
      } catch {
        return sendJson(res, 400, {
          error: { code: "invalid_json", message: "Request body is not valid JSON" },
        });
      }
    }

    const currentUser = () => {
      const token = bearerToken(req);
      return token ? sessions.get(token) : undefined;
    };

    if (method === "GET" && url.pathname === "/health") {
      return sendJson(res, 204, null);
    }

    if (method === "POST" && url.pathname === "/login") {
      if (body.email !== TEST_USER.email || body.password !== TEST_USER.password) {
        return sendJson(res, 401, {
          error: { code: "invalid_credentials", message: "Email or password is wrong" },
        });
      }

      const sessionId = `sess_${randomUUID()}`;
      sessions.set(sessionId, { id: TEST_USER.id, email: TEST_USER.email });
      return sendJson(res, 200, {
        sessionId,
        expiresIn: 3600,
        user: { id: TEST_USER.id, email: TEST_USER.email },
      });
    }

    if (method === "GET" && url.pathname === "/me") {
      const user = currentUser();
      if (!user) {
        return sendJson(res, 401, {
          error: { code: "unauthorized", message: "Bearer token is required" },
        });
      }
      return sendJson(res, 200, { user });
    }

    if (method === "POST" && url.pathname === "/orders") {
      const user = currentUser();
      if (!user) {
        return sendJson(res, 401, {
          error: { code: "unauthorized", message: "Bearer token is required" },
        });
      }

      const details = validateItems(body.items);
      if (details.length > 0) {
        return sendJson(res, 400, {
          error: { code: "validation_failed", message: "Order payload is invalid", details },
        });
      }

      const totalCents = body.items.reduce(
        (sum, item) => sum + item.quantity * item.priceCents,
        0,
      );
      const order = {
        id: `ord_${orderSeq++}`,
        userId: user.id,
        status: "created",
        totalCents,
        items: body.items,
      };
      orders.set(order.id, order);

      return sendJson(res, 201, { order }, { location: `/orders/${order.id}` });
    }

    const orderMatch = url.pathname.match(/^\/orders\/([^/]+)$/);
    if (method === "GET" && orderMatch) {
      const user = currentUser();
      if (!user) {
        return sendJson(res, 401, {
          error: { code: "unauthorized", message: "Bearer token is required" },
        });
      }

      const order = orders.get(orderMatch[1]);
      if (!order || order.userId !== user.id) {
        return sendJson(res, 404, {
          error: { code: "order_not_found", message: "Order was not found" },
        });
      }
      return sendJson(res, 200, { order });
    }

    if (method === "POST" && url.pathname === "/webhooks/payment") {
      if (req.headers["x-webhook-secret"] !== WEBHOOK_SECRET) {
        return sendJson(res, 401, {
          error: { code: "bad_signature", message: "Webhook signature is invalid" },
        });
      }

      if (typeof body.eventId !== "string" || typeof body.orderId !== "string") {
        return sendJson(res, 400, {
          error: { code: "validation_failed", message: "eventId and orderId are required" },
        });
      }

      if (webhookEvents.has(body.eventId)) {
        return sendJson(res, 200, { received: true, duplicate: true });
      }

      const order = orders.get(body.orderId);
      if (!order) {
        return sendJson(res, 404, {
          error: { code: "order_not_found", message: "Order was not found" },
        });
      }

      webhookEvents.add(body.eventId);
      order.status = "paid";
      return sendJson(res, 202, { received: true, duplicate: false });
    }

    return sendJson(res, 404, {
      error: { code: "route_not_found", message: `${method} ${url.pathname} is not supported` },
    });
  };
}

async function withServer(fn) {
  const server = createServer(makeApp());
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
  const address = server.address();
  const baseUrl = `http://127.0.0.1:${address.port}`;

  try {
    await fn(baseUrl);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
}

async function requestJson(baseUrl, path, options = {}) {
  const headers = { ...(options.headers ?? {}) };
  if (options.token) headers.authorization = `Bearer ${options.token}`;

  const init = {
    method: options.method ?? "GET",
    headers,
  };

  if (options.body !== undefined) {
    headers["content-type"] = "application/json";
    init.body = JSON.stringify(options.body);
  }

  const res = await fetch(`${baseUrl}${path}`, init);
  const text = await res.text();
  return { res, json: text ? JSON.parse(text) : null };
}

function expectKeys(value, keys) {
  for (const key of keys) {
    assert.ok(Object.prototype.hasOwnProperty.call(value, key), `missing key: ${key}`);
  }
}

async function login(baseUrl) {
  const { res, json } = await requestJson(baseUrl, "/login", {
    method: "POST",
    body: { email: TEST_USER.email, password: TEST_USER.password },
  });
  assert.equal(res.status, 200);
  assert.equal(typeof json.sessionId, "string");
  return json.sessionId;
}

async function createOrder(baseUrl, token) {
  const { res, json } = await requestJson(baseUrl, "/orders", {
    method: "POST",
    token,
    body: {
      items: [
        { sku: "book", quantity: 2, priceCents: 1500 },
        { sku: "video", quantity: 1, priceCents: 4000 },
      ],
    },
  });
  assert.equal(res.status, 201);
  assert.match(res.headers.get("location"), /^\/orders\/ord_/);
  expectKeys(json.order, ["id", "status", "totalCents", "items"]);
  assert.equal(json.order.totalCents, 7000);
  return json.order;
}

const tests = [];
function test(name, fn) {
  tests.push({ name, fn });
}

test("login/session smoke test", async (baseUrl) => {
  const health = await fetch(`${baseUrl}/health`);
  assert.equal(health.status, 204);

  const { res, json } = await requestJson(baseUrl, "/login", {
    method: "POST",
    body: { email: TEST_USER.email, password: TEST_USER.password },
  });
  assert.equal(res.status, 200);
  expectKeys(json, ["sessionId", "expiresIn", "user"]);
  assert.equal(json.user.email, TEST_USER.email);
  assert.equal(json.user.password, undefined);

  const me = await requestJson(baseUrl, "/me", { token: json.sessionId });
  assert.equal(me.res.status, 200);
  assert.equal(me.json.user.id, TEST_USER.id);
});

test("order creation API returns a stable JSON shape", async (baseUrl) => {
  const token = await login(baseUrl);
  const order = await createOrder(baseUrl, token);

  const detail = await requestJson(baseUrl, `/orders/${order.id}`, { token });
  assert.equal(detail.res.status, 200);
  assert.equal(detail.json.order.id, order.id);
  assert.equal(detail.json.order.status, "created");
});

test("payment webhook verifies signature and duplicate events", async (baseUrl) => {
  const token = await login(baseUrl);
  const order = await createOrder(baseUrl, token);

  const noSignature = await requestJson(baseUrl, "/webhooks/payment", {
    method: "POST",
    body: { eventId: "evt_1", orderId: order.id },
  });
  assert.equal(noSignature.res.status, 401);
  assert.equal(noSignature.json.error.code, "bad_signature");

  const accepted = await requestJson(baseUrl, "/webhooks/payment", {
    method: "POST",
    headers: { "x-webhook-secret": WEBHOOK_SECRET },
    body: { eventId: "evt_1", orderId: order.id },
  });
  assert.equal(accepted.res.status, 202);
  assert.equal(accepted.json.duplicate, false);

  const paidOrder = await requestJson(baseUrl, `/orders/${order.id}`, { token });
  assert.equal(paidOrder.json.order.status, "paid");

  const duplicate = await requestJson(baseUrl, "/webhooks/payment", {
    method: "POST",
    headers: { "x-webhook-secret": WEBHOOK_SECRET },
    body: { eventId: "evt_1", orderId: order.id },
  });
  assert.equal(duplicate.res.status, 200);
  assert.equal(duplicate.json.duplicate, true);
});

test("regression tests cover auth, validation, and not-found bugs", async (baseUrl) => {
  const badLogin = await requestJson(baseUrl, "/login", {
    method: "POST",
    body: { email: TEST_USER.email, password: "wrong" },
  });
  assert.equal(badLogin.res.status, 401);
  assert.equal(badLogin.json.error.code, "invalid_credentials");

  const missingAuth = await requestJson(baseUrl, "/orders", {
    method: "POST",
    body: { items: [{ sku: "book", quantity: 1, priceCents: 1500 }] },
  });
  assert.equal(missingAuth.res.status, 401);

  const token = await login(baseUrl);
  const invalidOrder = await requestJson(baseUrl, "/orders", {
    method: "POST",
    token,
    body: { items: [{ sku: "book", quantity: 0, priceCents: 1500 }] },
  });
  assert.equal(invalidOrder.res.status, 400);
  assert.equal(invalidOrder.json.error.code, "validation_failed");
  assert.ok(Array.isArray(invalidOrder.json.error.details));

  const missingOrder = await requestJson(baseUrl, "/orders/ord_missing", { token });
  assert.equal(missingOrder.res.status, 404);
  assert.equal(missingOrder.json.error.code, "order_not_found");
});

await withServer(async (baseUrl) => {
  let failed = 0;

  for (const { name, fn } of tests) {
    try {
      await fn(baseUrl);
      console.log(`ok - ${name}`);
    } catch (error) {
      failed += 1;
      console.error(`not ok - ${name}`);
      console.error(error);
    }
  }

  if (failed > 0) {
    process.exitCode = 1;
  }
});

Run command:

node api-smoke.test.mjs

Success पर 4 ok lines दिखेंगी। यह safe local mock है, production test नहीं। Real project में makeApp() को local app server, staging URL, या Playwright request fixture से बदल सकते हैं। Assertions वही रखें: status code, JSON shape, auth boundary, negative behavior और no leaked secrets।

Claude Code Prompt

Claude Code को target और failure rules दोनों दें। यह prompt एक single happy path से बचाता है।

Add API tests for these flows:
- login and session check
- order creation API
- payment webhook
- regression coverage for the last bug

Must verify:
- success status codes and JSON shape
- missing auth, invalid input, unknown ID, and missing webhook signature
- password, tokens, and secrets are never returned or logged
- test data does not collide across parallel test runs
- a command that CI can run

After the edit, summarize which incidents the tests would catch and which command verifies them.

अगर OpenAPI file है, तो लिखें कि OpenAPI को contract माना जाए। Contract test का अर्थ है कि public promise और actual response एक जैसे रहें।

openapi: 3.1.0
info:
  title: Local Orders API
  version: 1.0.0
paths:
  /orders:
    post:
      responses:
        "201":
          description: Order created
          content:
            application/json:
              schema:
                type: object
                required: [order]
                properties:
                  order:
                    type: object
                    required: [id, status, totalCents, items]

यह छोटा fragment बताता है कि creation 201 देगा, response order में wrapped होगा, और required fields मौजूद होंगे। Review करते समय implementation, OpenAPI, tests और README को साथ देखें।

Test Data और CI

API tests की stability अक्सर data पर निर्भर करती है। अगर हर test वही demo@example.com, वही order ID और वही webhook event ID इस्तेमाल करे, तो parallel CI flaky होगा। Disposable IDs, per-run DB reset, या local mock का उपयोग करें।

name: api-tests
on:
  pull_request:
  push:
    branches: [main]

jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: node api-smoke.test.mjs

Fast API smoke tests को CI की शुरुआत में चलाएं। इससे auth, JSON और webhook regression browser tests से पहले पकड़े जाते हैं। Staging call हो तो logs में token, cookie, webhook signature या API key print न करने का rule जोड़ें।

Common Pitfalls

पहला pitfall है केवल 200 OK check करना। Required fields, forbidden fields, error format और failure status codes भी check करें।

दूसरा है shared test data। एक global demo user tests को order-dependent और flaky बनाता है।

तीसरा है secrets in logs। Authorization, cookies और webhook signatures mask होने चाहिए।

चौथा है flaky external dependency। Payment, email और CRM को daily CI में mock करें, और release से पहले limited staging check रखें।

पांचवां है no negative tests। Missing auth, invalid payload, unknown ID, forbidden role और unsigned webhook API contract का हिस्सा हैं।

छठा है केवल mocks पर भरोसा। Mock fast है, लेकिन real headers, timeout, error envelope और status code difference छिपा सकता है।

Summary और CTA

Claude Code से अच्छे API tests पाने के लिए पहले वह promise लिखें जिसे protect करना है: login stable रहे, order creation सही रहे, webhook verify हो, और पुराने bugs regression tests से ढके रहें। फिर Claude Code से code और CI command बनवाएं।

Team rollout में असली benefit standardization है: prompts, OpenAPI updates, review criteria, CI gates और failure diagnosis। Real repository के लिए help चाहिए तो Claude Code training and consultation देखें। Solo builders free cheatsheet और इस article के prompt से शुरू कर सकते हैं।

Masa ने इस workflow को ऊपर दिए local Node server पर चलाकर verify किया। Practical result यह था कि login, order creation, webhook verification और regression coverage एक short command में आ गए: node api-smoke.test.mjs। यह 200 OK से ज्यादा पकड़ता है: leaked password, missing auth, unsigned webhook, invalid order payload और duplicate webhook event।

#Claude Code #API testing #automation #testing #quality assurance
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.