I don't start by checking whether the output is good. I start by checking whether it is the right shape.
That is it. A schema check. All required fields present, types correct, no extra fields the model invented on its own.
I know how unimpressive that sounds. Months of testing AI agents in production and my opening
move is one boring expect(output).toMatchSchema().
But the model rarely fails by being stupid. It fails by being creative. The wording is fine, the answer sounds smart, and then one day there is a field in the response that nobody asked for, and some downstream code that trusted the shape quietly falls over.
The matcher isn't built into Jest — it comes from a package called
jest-json-schema, and expect.extend does the wiring:
npm install --save-dev jest-json-schema @types/jest-json-schema
The types package matters if you're on TypeScript. Without it the code runs perfectly but the
compiler complains that toMatchSchema doesn't exist, because TypeScript only trusts
matchers it has been told about.
// simplified example — real schemas are bigger, the idea is identical
import { matchers } from 'jest-json-schema';
import { extractMovieFields, sampleReview } from './extract';
expect.extend(matchers);
const movieSchema = {
type: 'object',
required: ['title', 'director', 'rating', 'release_date'],
// catches the fields the model invents
additionalProperties: false,
properties: {
title: { type: 'string' },
director: { type: 'string' },
rating: { type: 'number', minimum: 0, maximum: 10 },
release_date: { type: 'string', format: 'date' },
},
};
test('AI extraction returns the shape it promised', async () => {
const output = await extractMovieFields(sampleReview);
// not checking if the answer is good yet — only if it's the right shape
expect(output).toMatchSchema(movieSchema);
});
The line doing the real work is additionalProperties: false. Required fields catch
what the model forgot. That line catches what it made up, which is the harder failure to notice
in review.
Make the model misbehave on purpose — drop a required field, let it invent one nobody asked for — and Jest tells you exactly what happened:
● AI extraction returns the shape it promised
received
must have required property 'release_date'
must NOT have additional properties, but found 'sequel_title'
It hallucinated a sequel. Caught in milliseconds, with no eval framework and no golden dataset.
The clever evaluation work matters too — semantic similarity, LLM-as-judge, human review panels. It just comes second. Took me a while to accept that.
Most teams start at step four because it is the interesting one. Then they spend a quarter building an evaluation harness while a hallucinated field sits in production.
Boring test first. Clever tests after.
If you're making the move from manual testing into automation, the 90-day roadmap is the place to start. I post twice a week on LinkedIn.