DEV Community

Cover image for Contract Testing in 10 Lines: JSON Schema Validation in Postman
Imran Al Munyeem
Imran Al Munyeem

Posted on • Originally published at imranalmunyeem.github.io

Contract Testing in 10 Lines: JSON Schema Validation in Postman

Here's a bug your test suite probably wouldn't catch.

A backend developer refactors the user model. The id field — an integer since forever — starts coming back as a string: "42" instead of 42. Every value is still "correct". Your assertion pm.expect(user.id).to.eql(42) fails, sure — but only on the one endpoint you asserted id on, not the other nine that return users. Meanwhile three client apps that did user.id + 1 are now computing "421".

That's structural drift, and it's what actually breaks API consumers: renamed fields, changed types, properties that quietly vanish. Field-by-field value assertions catch it patchily and by accident. Schema validation catches it systematically — and in Postman it costs about ten lines, because the ajv JSON-schema validator is built into the script sandbox.

The ten lines

In Scripts → Post-response on any request that returns a user:

const userSchema = {
    type: "object",
    required: ["id", "name", "email"],
    properties: {
        id:    { type: "integer" },
        name:  { type: "string" },
        email: { type: "string", pattern: "@" }
    }
};

pm.test("Response matches the user schema", () => {
    pm.expect(pm.response.json()).to.be.jsonSchema(userSchema);
});
Enter fullscreen mode Exit fullscreen mode

That single test now fails if id becomes a string, if email disappears, if name becomes an object — every structural mutation, whether or not you thought to assert on that field's value.

For an endpoint returning an array of users:

const userListSchema = {
    type: "array",
    minItems: 1,
    items: userSchema   // reuse the object schema
};

pm.test("List matches schema", () => {
    pm.expect(pm.response.json()).to.be.jsonSchema(userListSchema);
});
Enter fullscreen mode Exit fullscreen mode

Share one schema across every endpoint

The real power move: your API returns users from /users, /users/:id, /login, /teams/:id/members… and they should all be the same shape. Store the schema once as a collection variable (JSON, stringified), and every request validates against the same contract:

// One-time setup — e.g. in the collection's Pre-request script:
pm.collectionVariables.set("userSchema", JSON.stringify({
    type: "object",
    required: ["id", "name", "email"],
    properties: {
        id:    { type: "integer" },
        name:  { type: "string" },
        email: { type: "string", pattern: "@" }
    }
}));
Enter fullscreen mode Exit fullscreen mode
// In any request's Post-response script:
const schema = JSON.parse(pm.collectionVariables.get("userSchema"));

pm.test("User matches the shared contract", () => {
    pm.expect(pm.response.json()).to.be.jsonSchema(schema);
});
Enter fullscreen mode Exit fullscreen mode

Now when the contract legitimately changes — a new required field, say — you update one variable and every endpoint's test updates with it. That's contract testing: not a new tool, just the discipline of asserting shape centrally.

Schema tips that save real debugging time

required is where the protection lives. JSON Schema ignores missing properties unless they're listed in required. A schema without required validates {} happily — the most common reason people think their schema test "isn't working".

Type-check numbers deliberately. integer vs number matters — prices and quantities have different rules. And remember the drift that motivates all this is usually number→string, so never write type: ["integer", "string"] to make a flaky test pass. That's deleting the alarm.

Formats catch bad data early. pattern for emails and IDs, enum for status fields:

status: { type: "string", enum: ["active", "suspended", "deleted"] }
Enter fullscreen mode Exit fullscreen mode

When someone adds a fourth status without telling anyone, you find out from a red test instead of a production incident.

Start schemas from reality, not from scratch. Send the request once, copy the response, and derive the schema from it (an LLM does this conversion well — then review it against the spec, especially which fields are truly required; a schema generated from one happy response will mark optional fields as required and miss fields that were null that day).

Where this sits in a strategy

Schema validation is one layer of a complete endpoint checklist — protocol (status, headers, timing), contract (this), data values, behaviour (state actually changed), and security (auth negatives). It's the layer with the best effort-to-protection ratio in the whole list: ten lines per shape, and an entire category of consumer-breaking regressions becomes impossible to ship silently.


Adapted from Chapter 7 of my free, open-source book *API Testing Using Postman: The Practical Guide to Modern API Testing*. Read online, grab the PDF/EPUB, or contribute on GitHub.

I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at imranalmunyeem.com.

Top comments (0)