Contract testing 5 min read

Your tests are green. Two of your seven operations were never compared.

Our own contract run passed. Here is what it told us anyway.

Every test report is built to answer one question: did anything fail? It is the wrong question to stop at, because a report that only knows how to say pass or fail has no vocabulary for the third answer, which is nobody looked.

That third answer is where the risk lives. Below is a real run, published on every commit, with no login and no sales call in front of it.

The result

Result

MATCHED, WHERE TESTED

The mock and the provider gave the same answers in 12 compared scenarios. This run compared 5 of 7 operations. It did not test 2.

12

scenarios matched

2

operations not tested

54

expected differences

Read the result word again. Not passed. Matched, where tested. The qualifier is doing real work, and most reports would have dropped it.

What a normal report would have said

Twelve scenarios ran. Twelve passed. Rendered the usual way, that is a green tick and a 100% badge, and you would move on.

But 100% of what? The suite exercised 5 of the 7 operations the OpenAPI document declares. The other two were never called, so nothing in that run knows anything about them. They were not fine. They were not broken. They were unexamined, which is a different state, and the honest report keeps it separate.

An agreement figure without its denominator is the most common way a test report misleads a room. "100% divergence-free" over five of forty operations reads identically to genuine coverage on a slide.

It names the gap

The part worth stealing, whatever tool you use, is that the report does not stop at flagging the gap. It hands you the work by name:

Next

  1. 1 Add a test for openapi:cancelPolicy.
    This run did not exercise it, so nothing here reports whether the mock matches the provider for it.
  2. 2 Add a test for openapi:getClaim.
    Same again.
  3. 3 Review 3 ignore rules.
    They waived 54 differences. Each waiver carries a reason and an owner.
  4. 4 Record which deployment you tested.
    The wire shows only that the responder was not one of our mocks.

Two named operations is a ticket someone can pick up this afternoon. "Coverage is at 71%" is not.

The 54 differences that are not failures

Fifty-four responses differed between the mock and the provider, and none of them are a problem. They are minted identifiers: a surrogate primary key, a quote reference, a claim's policy reference. Each target generates its own, so they will never match, and pretending otherwise would drown every real finding in noise.

Three ignore rules cover them, and each carries a reason and an owner. Crucially the rule re-classifies the difference rather than hiding it: both values are still recorded, and a rule that stops matching anything is flagged stale so the list cannot quietly rot. In this run, none are stale.

The distinction that matters: a waived difference is one you have accounted for. A suppressed difference is one you have stopped seeing.

The claim it will not make

The run reached rung 3 of 4, verified-against-provider. Not rung 4. It is capped there, and the report says exactly why: the pair did not exercise 2 of the 7 declared operations, so the artifact cannot claim the top rung. That limit holds even though every operation it did compare was divergence-free.

A tool willing to cap its own best-case output is telling you something about how the rest of its numbers were arrived at.

What we would want you to take from this

None of this requires our tooling. It requires deciding that a test report exists to tell you what you actually know, which is a narrower and more useful thing than what passed.

See the run

Public, no licence, no install. Regenerated by CI on every push, so the numbers above may have moved by the time you read this. That is the point of computing freshness at read time.

How the paired run works, the six outcome classes, and the rung ladder are covered on contract testing. The same refusal to score what was not measured, applied to a specification rather than a run, is API governance.

Find out where your mock and your provider disagree.

One suite, two targets, one session. No broker, no pact file.