# Integration and Contract Tests

Course: [Testing & Verification](https://www.devtutorials.ai/learn/testing-verification) · Section: Verifying AI Changes
Source: https://www.devtutorials.ai/learn/testing-verification/verifying-ai-changes/integration-and-contract-tests

> See an over-mocked unit test pass while the real API changes. Catch it with an integration test and a contract test, make contracts strict, keep mocks honest, and learn what to mock and what to run for real.

## When Parts Meet

Most apps have parts that talk to each other:

- the **frontend** (the page) asks the **backend** (the server) for data
- your code calls a payment service
- one team's API is used by another team's code

Each part can pass its own tests and still break when they meet.

In this lesson, you will see a unit test stay green while the real API changes. Then you will add the two kinds of test that catch it: an **integration test** and a **contract test**.

**Simple lesson:** parts that work alone can still fail together.

### A Real-World Example

Think about a plug and a socket.

- The plug factory tests its plugs. Every plug is perfect.
- The socket factory tests its sockets. Every socket is perfect.
- But one factory changed from round pins to flat pins.
- At home, the plug does not fit the socket.

What would have caught it?

1. **Trying the real plug in the real socket.** That is an integration test.
2. **A written shape that both factories check against.** That is a contract.

## What You Will Learn

In this lesson, you will learn:

1. How a mock can hide a real API change.
2. What an **integration test** is, and when to write one.
3. What a **contract** and a **contract test** are.
4. How to make a contract strict enough to catch changes.
5. How to keep your mocks honest with the same contract.
6. When to mock and when to use the real code.

## The Mock That Hid the Change

### The Setup

The order page shows the order total, like "₹330.00".

- **`src/server.ts`** is the backend. It answers `GET /orders/7`.
- **`src/client.ts`** is the frontend code. It reads `order.total`.
- **`src/http.ts`** is the lab's network. It passes the request to the server.

The backend team just changed the API. `total` is now `totalPaise`. Nobody told the frontend.

Press **▶ Run tests**. Green.

- **The test:** a mock (`vi.fn().mockResolvedValue(...)`) returns `{ id: 7, total: 330 }`.
- **The real server:** now returns `totalPaise: 33000`. Open `src/server.ts` to see it.
- **Why it is green:** the test never talks to the real server. The mock still has the old shape.

**In the real world,** the network call goes over HTTP to a running server. **In this lab,** `src/http.ts` passes the request straight to the server function, in your browser. The tests you write are the same.

**Simple lesson:** a mock shows the API as it was when someone wrote the mock, not as it is today.

## Integration Tests

**Integration test** = a test where two or more real parts run together.

Press **▶ Run tests**.

- **The Tests tab:** red. "Cannot read properties of undefined (reading 'toFixed')".
- **Why:** with the real server, `order.total` does not exist any more.
- **Now try:** open `src/server.ts` and change `totalPaise: 33000` back to `total: 330`. Run again. Green.

Integration tests are slower and need more set-up than unit tests. Use them where parts meet: your API, your database, a service you call.

**Simple lesson:** unit tests check each part. Integration tests check that the parts fit.

## Contract Tests

### What a Contract Is

**Contract** = a written agreement about the shape of the data two parts send each other.

For the order API, the contract says:

- `id` is a whole number
- `total` is a number, 0 or more
- `currency` is the text "INR"
- all three must be there

It is often written as a **JSON Schema**. That is a standard way to describe the shape of JSON data.

**Contract test** = a test that checks real data against the contract.

Press **▶ Run tests**.

- **The Tests tab:** red. The list says `$.total is missing`.
- **Why it helps:** the backend team would see this the moment they renamed the field, before the frontend broke.
- **Open `src/order-contract.ts`** to read the contract.

<figure className="ver-fig">
<svg viewBox="0 0 640 200" role="img" aria-labelledby="ver-fig-contract" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="ver-fig-contract">Animation: the server sends a response with totalPaise. It is held against the contract shape which needs total. The missing piece turns red and the contract test fails before the page breaks.</title>
  <g style={{ fontFamily: "var(--sans)", fontSize: 13 }}>
    <rect x="20" y="50" width="150" height="100" rx="12" style={{ fill: "var(--ver-slate-soft)", stroke: "var(--ver-slate)" }} />
    <text x="95" y="80" textAnchor="middle" style={{ fill: "var(--ink)", fontWeight: 700 }}>Server</text>
    <text x="95" y="105" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 11 }}>totalPaise: 33000</text>
    <rect x="245" y="35" width="150" height="130" rx="12" style={{ fill: "var(--ver-blue-soft)", stroke: "var(--ver-blue)", strokeWidth: 2 }} />
    <text x="320" y="60" textAnchor="middle" style={{ fill: "var(--ver-blue)", fontWeight: 700 }}>Contract</text>
    <g style={{ fontFamily: "var(--mono)", fontSize: 11 }}>
      <text x="262" y="88" style={{ fill: "var(--ver-green)" }}>✓ id</text>
      <text x="262" y="110" style={{ fill: "var(--ink)" }}>? total<animate attributeName="fill" values="var(--ink);var(--ink);var(--ver-red);var(--ver-red)" keyTimes="0;0.45;0.5;1" dur="7s" repeatCount="indefinite" /></text>
      <text x="262" y="132" style={{ fill: "var(--ver-green)" }}>✓ currency</text>
    </g>
    <rect x="470" y="50" width="150" height="100" rx="12" style={{ fill: "var(--panel)", stroke: "var(--ver-slate)" }} />
    <text x="545" y="80" textAnchor="middle" style={{ fill: "var(--ink)", fontWeight: 700 }}>Order page</text>
    <text x="545" y="105" textAnchor="middle" style={{ fill: "var(--ver-green)", fontSize: 12 }}>safe: change caught</text>
  </g>
  <circle r="8" cy="100" style={{ fill: "var(--accent)" }}>
    <animate attributeName="cx" values="170;245;245" keyTimes="0;0.35;1" dur="7s" repeatCount="indefinite" />
  </circle>
  <text x="320" y="190" textAnchor="middle" style={{ fill: "var(--ver-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }} opacity="0">$.total is missing → contract test fails<animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.5;0.55;1" dur="7s" repeatCount="indefinite" /></text>
</svg>
<figcaption>The contract stops the changed response before it reaches the page.</figcaption>
</figure>

### A Contract That Is Too Loose

A contract only helps if it says enough.

Press **▶ Run tests**. Green, even though `total` is missing.

- **Why:** this contract has no `required` list. Missing fields are allowed.
- **Now try:** add `required: ['id', 'total', 'currency'],` and a `currency` property. Run again. Red.

**A useful contract says:**

- which fields are **required**
- the **type** of each field (`integer`, `number`, `string`)
- allowed values, with `enum` or `minimum`, where they matter

### Keep the Mock Honest

Mocks are still useful. They make unit tests fast. The trick is to check the mock's fake data against the same contract.

Press **▶ Run tests**. Green.

- **Test 1:** checks the fake order matches the contract.
- **Test 2:** uses the fake order in a fast unit test.
- **If the contract changes,** test 1 fails, and you know to update the mock.

**Simple lesson:** one contract, checked on both sides, keeps the server, the client and the mocks in step.

## When to Mock

Mock only what is outside your code, slow, costly or changing on its own.

**Mock these:** the network to other companies, the clock, random numbers, emails and payments.

**Do not mock these:** your own small functions, plain maths, and above all the code the test is about.

Agents often mock more than they need to. When you review an AI test with `vi.fn()`, ask: does the real code still run?

**Simple lesson:** mock the edges, not the middle.

## TL;DR

1. **Integration test** = real parts running together, like the client and the server.
2. **Contract** = the agreed shape of the data between two parts.
3. **JSON Schema** = a standard way to write that shape.
4. **Contract test** = checks real data against the contract.
5. **A good contract** lists the required fields, the types and the allowed values.
6. **Honest mock** = fake data that also passes the contract.
7. **Mock the edges** (network, clock, email, payments), **not the middle** (your own code).

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Only mocked tests | "The unit test passes, so the page works." | Add one integration test where parts meet. | An API change breaks the page in production. |
| A loose contract | "Listing the fields is enough." | Mark them `required` and give types. | A missing field still passes. |
| Mocks that drift | "I wrote the mock once. It is fine." | Check mock data against the contract. | Mocks keep the old shape for months. |
| Mocking your own maths | "Mock everything except one line." | Let your own code run. | Real bugs hide behind the mock. |
| Contract on one side only | "The server team checks it." | Check it on both sides. | The client and the mock drift apart. |

## Check Your Understanding

## Exercises

## What to Remember

- A mock shows the API as it was, not as it is.
- Write an integration test where your parts meet.
- A contract is the agreed shape of the data. Check it with a contract test.
- Make contracts strict: required fields, types, allowed values.
- Check your mocks against the same contract.
- Mock the edges, not your own code.

## Further Reading

- [JSON Schema: getting started](https://json-schema.org/learn/getting-started-step-by-step): how to write contracts.
- [Vitest mocking guide](https://vitest.dev/guide/mocking.html): mocks, and how to keep them small.

Next: **User-Flow Checks**. Every unit test passes, but checkout is broken. You will catch it with a flow test, then export your first verification pack.
