> For the complete documentation index, see [llms.txt](https://learning.contextqa.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://learning.contextqa.com/testing-with-ai-coding-agents/integration-testing.md).

# Integration Testing with AI Agents

Test real HTTP and application boundaries with Claude Code, Cursor, or Codex, then use ContextQA MCP for deployed API and workflow checks.

## Quick answer

Ask your coding agent to identify the real boundary at risk, provision isolated test dependencies, and assert both the response and the relevant state. Run the test with your repository's runner. For deployed API coverage, ContextQA MCP can generate cases from an OpenAPI specification and retrieve execution evidence. A test with every dependency mocked does not prove those integrations work.

An integration test might connect an HTTP client to a real service, a repository layer to a real test database, or a consumer to a sandbox provider. State precisely which boundary is exercised. This guide's runnable example covers HTTP serialization, request validation, and a shipping policy function; it deliberately does not claim database, payment, or production integration coverage.

## Prerequisites and scope

Use a checkout with an established test command, documented API behavior, and isolated dependencies. For database tests, know how to create, reset, and dispose of the test database. For provider tests, use a sandbox account with approved credentials and a controlled quota. Avoid shared identifiers that let concurrent jobs overwrite one another's data.

The local example requires Node.js 24 or later, uses only built-in modules, and starts a server on an available loopback port. No external service or ContextQA account is required. If you already use Playwright Test, its request client can also test HTTP APIs and prepare or verify application state. [Playwright API testing](https://playwright.dev/docs/api-testing).

## 1. Define the boundary and observable contract

For an illustrative shipping API, `POST /quote` accepts JSON containing `subtotalCents` and `country`. Valid input returns an exact shipping fee. Invalid JSON returns 400; valid JSON with an invalid value returns 422. Other paths return 404. The fee policy is the one in the [unit-testing guide](/testing-with-ai-coding-agents/unit-testing.md).

Write down what this does **not** exercise: authentication, a database, deployment networking, tax, or carrier pricing. Those need their own requirements and test environments. This scope statement prevents a small local green result from becoming an unsupported release claim.

For an actual database integration, add an assertion using a new transaction or independent read connection after the write. An HTTP 201 alone cannot prove that the intended record was committed with the correct tenant and values.

## 2. Give the agent the contract and lifecycle

```
Inspect our API implementation, schema, existing integration runner,
and test-environment setup. Identify which boundaries are real and
which are mocked. Do not label fully mocked interactions integration.

Add tests for valid requests, malformed JSON, invalid values, and
unsupported routes. Assert exact response bodies, not status alone.
For persisted operations, verify committed state through an independent
read and clean up only records created by this test run.

Use an isolated environment and unique data. Await startup, use bounded
request timeouts, and close connections even when assertions fail.
Run the focused suite and report commands, exit codes, actual boundaries,
failed cases, and any dependency that prevented execution.
```

Tell [Claude Code](/testing-with-ai-coding-agents/claude-code-testing.md), [Cursor](/testing-with-ai-coding-agents/cursor-testing.md), or [Codex](/testing-with-ai-coding-agents/codex-testing.md) which fixture/reset commands already exist. Do not let the agent silently replace an unavailable real dependency with a mock and keep the original coverage claim.

## 3. Build a small real HTTP fixture

Copy `shipping.mjs` from the unit guide into a local directory. Save this illustrative application adapter beside it as `quote-server.mjs`:

```js
import { createServer } from 'node:http';
import { shippingFee } from './shipping.mjs';

export function createQuoteServer() {
  return createServer(async (req, res) => {
    const send = (status, body) => {
      res.writeHead(status, { 'content-type': 'application/json' });
      res.end(JSON.stringify(body));
    };
    if (req.method !== 'POST' || req.url !== '/quote') {
      req.resume();
      return send(404, { error: 'not found' });
    }
    let raw = '';
    for await (const chunk of req) raw += chunk;
    let input;
    try { input = JSON.parse(raw); }
    catch { return send(400, { error: 'invalid JSON' }); }
    try {
      const shippingCents = shippingFee(input?.subtotalCents, input?.country);
      return send(200, { shippingCents });
    } catch (error) {
      if (error instanceof TypeError || error instanceof RangeError) {
        return send(422, { error: 'invalid quote input' });
      }
      throw error;
    }
  });
}
```

This intentionally small, loopback-only teaching server is not a production API template: it has no authentication, body-size limit, or operational error middleware. In your application, import the real server factory rather than copying this handler into tests.

Save `quote.integration.test.mjs` beside it:

```js
import test from 'node:test';
import assert from 'node:assert/strict';
import { once } from 'node:events';
import { createQuoteServer } from './quote-server.mjs';

test('HTTP quote contract', async (t) => {
  const server = createQuoteServer();
  t.after(() => new Promise((resolve, reject) => {
    server.close((error) => error ? reject(error) : resolve());
  }));
  server.listen(0, '127.0.0.1');
  await once(server, 'listening');
  const base = `http://127.0.0.1:${server.address().port}`;
  const cases = [
    ['/quote', '{"subtotalCents":4999,"country":"US"}', 200, { shippingCents: 500 }],
    ['/quote', '{"subtotalCents":5000,"country":"US"}', 200, { shippingCents: 0 }],
    ['/quote', '{"subtotalCents":-1,"country":"US"}', 422, { error: 'invalid quote input' }],
    ['/quote', '{', 400, { error: 'invalid JSON' }],
    ['/missing', '{}', 404, { error: 'not found' }],
  ];
  for (const [path, body, status, expected] of cases) {
    const response = await fetch(base + path, {
      method: 'POST', headers: { 'content-type': 'application/json' },
      body, signal: AbortSignal.timeout(3000),
    });
    assert.equal(response.status, status, `${path}: ${body}`);
    assert.deepEqual(await response.json(), expected);
  }
});
```

Run:

```bash
node --test quote.integration.test.mjs
```

The request uses an actual HTTP socket. Replacing `fetch` with a stub returning the expected body would remove the boundary this example is intended to test. Node's runner awaits an async test's returned promise; preserve that lifecycle when adapting the fixture. [Node test runner](https://nodejs.org/api/test.html).

## 4. Add the risks your integration actually has

Extend the contract rather than adding generic cases indiscriminately:

| Boundary                   | Useful negative or boundary checks                                                                |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| Authentication and tenancy | Missing token; expired token; another tenant's resource ID; correct denial with no data change    |
| Database write             | Duplicate key; transaction rollback; concurrent updates; committed values read back independently |
| External provider          | Timeout; documented error response; rate limit; idempotent retry; no duplicate side effect        |
| Event or queue             | Schema mismatch; duplicate delivery; delayed event; final state within an explicit deadline       |

For eventually consistent systems, poll a meaningful state with a finite deadline and report the last observation. A fixed sleep may be too short on a slow run and wastes time on a fast one. An absent sandbox or unavailable dependency is a blocked test, not a passing result.

## 5. Add ContextQA MCP coverage for the deployed boundary

[Set up ContextQA MCP](/mcp-server/installation-and-setup.md) and confirm the connected workspace. The documented `generate_tests_from_swagger` input is `file_path_or_url`. Review the connected tool's actual schema before supplying a local path: a hosted tool may not share your computer's filesystem. Use a specification source the configured tool can access without publishing private API details. [Generation tool reference](/mcp-server/tool-reference/test-generation.md).

```
Using ContextQA MCP and our approved staging OpenAPI specification,
prepare cases for the quote endpoint: valid subtotal, threshold,
invalid subtotal, malformed JSON, and the documented authentication
rules. Show generated steps and assertions, including exact response
fields. Flag requirements absent from the specification.

Execute only reviewed cases against staging. Retrieve each execution's
result ID and step results. Report actual assertion outcomes separately
from completion. Do not claim database persistence unless an authorized
read-back step verifies it. Keep blocked or unsupported checks explicit.
```

Use the documented create/run/result sequence from [execution and results](/mcp-server/tool-reference/execution-and-results.md). ContextQA cases and native repository integration tests remain separately runnable assets. For a workflow combining browser actions and API checks, continue with [end-to-end testing](/testing-with-ai-coding-agents/end-to-end-testing.md).

## Debugging and verification

When a test fails, preserve the request shape, status, relevant response, environment, and correlation ID without exposing tokens. Check connectivity before changing assertions. If a response is wrong, separate transport failure, contract mismatch, and incorrect business behavior. Rerun after a fix with the original failure attached; a retry does not invalidate the first observation.

## Common questions

### Is an API test always an integration test?

No. A client tested entirely against a mocked response checks client behavior. A real HTTP request to the intended service exercises that boundary; a database or external provider is covered only if it participates.

### Does OpenAPI generation prove all business rules?

No. The specification may omit permissions, side effects, concurrency, or domain rules. Add those requirements and verify the generated assertions.

### Can I export these tests?

Keep native tests in Git. For ContextQA browser artifacts, follow [export and portability](/testing-with-ai-coding-agents/export-and-portability.md) and inspect the generated project; do not assume every API fixture or provider setup transfers unchanged.

**Next step:** [Connect the MCP server](/mcp-server/installation-and-setup.md), then [book an integration-testing demo](https://contextqa.com/book-a-demo/?utm_source=learning.contextqa.com\&utm_medium=referral\&utm_campaign=docs_demand_gen\&utm_content=integration-testing) using one API contract and its downstream state checks.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://learning.contextqa.com/testing-with-ai-coding-agents/integration-testing.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
