# A unit test cannot find a contract mismatch

[Journal](/journal/)

July 30, 2026 · [guides](/journal/#guides)



Client fixtures can preserve a false assumption when neither the validator nor its unit suite reads the server contract.

![Two matching client gauges feed a contract frame that rejects a differently nested server envelope, with five green defect markers.](/_astro/a-unit-test-cannot-find-a-contract-mismatch.DPCnuyba_x1wIN.avif)

A validator and its unit suite can agree on the same false contract. Ours did for weeks in Muniment Mobile, while two ordinary routes failed at runtime. A green suite meant only that our fixtures repeated our client assumptions.

## Two green suites tested themselves

Our thread-create client expected a bare thread record. The server contract serves an envelope with one `thread` property around that record.

Our thread-detail client made the same envelope mistake. It also required a `messages` array, although that route does not serve messages. A separate page route serves them.

Every create attempt therefore ended in our parse error. Every attempt to open a thread met the same result.

The unit suites never disagreed. Each fixture came from the shape its client already expected, so each validator proved its own assumption.

Our only real-server check was a device journey on an emulator. It had never passed sign-in, so it never reached either thread route.

## The contract exposed five defects

We started with a field-by-field reading of the published contract. The [OpenAPI Specification defines the vocabulary for these schema rules](https://spec.openapis.org/oas/latest.html): required properties, nullability, enum members, and `additionalProperties`.

That comparison found five contract defects in our clients.

| Client area | What the client expected | What the contract serves |
| --- | --- | --- |
| Thread create | A bare thread record | An envelope containing `thread` |
| Thread detail | A bare thread record | An envelope containing `thread` |
| Thread detail messages | A required `messages` array | No `messages` property |
| Receipt mapper | No model name or watched duration | Both receipt fields |
| Entitlements | Three incomplete vocabulary sets | One more enum member in each set |

Our receipt mapper dropped the model name and watched duration that the contract serves. A reopened thread then lost details the user had just watched arrive.

Our entitlement validator also held three vocabulary sets. Each set lacked one published enum member. One missing grant kind could reject the whole snapshot, leaving the account screen empty.

## A route probe is not contract verification

A fixture copied from client code tests the client against itself. A schema-derived or server-derived fixture can fail when the client and contract disagree.

Status checks left the same blind spot. We had probed these routes for weeks without comparing a response body with its schema.

This evidence comes from our Muniment Mobile note pinned to commit `48683099d3263615e7e109827821e1f58669ae31`. It records the five defects above and the limits of our check.

Our client reading continues, so this pass does not prove that every remaining client matches its schema. We measured no latency change and no bundle change.

The device journey stays necessary. A schema reading cannot catch a route that matches its published shape but fails when the application calls it.

## Sources

1.  [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) spec.openapis.org
