AI and Contracts: Generating Tests the Types Already Describe In short: A discriminated union with three branches tells a model exactly how many test cases exist. A component with four booleans tells it nothing, so it invents. The quality of AI-generated tests is decided by your types, not your prompt. The last three lessons were about designing contracts. This one is about the payoff — and about the specific way AI-assisted testing goes wrong when the contract is loose. Why a strict contract changes what a model can do Compare two components. The loose one: Ask for "a full test suite" and the model has to guess. How many states are there? Sixteen, technically. Which of those are real? It cannot tell — the type system says all of them are legal. So it writes tests for the four it recognises by name, invents because that is a valid combination, and asserts something plausible about it. That test now encodes a state your team considers a bug as if it were expected behaviour, and the next person to fix the bug will break the test and "fix" the fix. The strict one: Now the answer is not a guess. There are three branches, one of which splits on whether the array is empty. Four cases. Every one is constructible, and no other case can be constructed at all. The point generalises: narrow types turn test generation from invention into translation. The model is no longer designing your test plan; it is transcribing a plan your types already contain. A workflow that works The order matters more than the prompt wording. Step 1 — Give it the contract, not the component Getting the enumeration first is the cheap check. If the list has three rows or six, the model has misread the contract and every test after that would be wrong. You find out in five seconds instead of after reviewing 200 lines of test code. Step 2 — Ask for tests against the enumeration The third rule is the important one, and we come back to it below. Step 3 — Make it run its own tests This is where the Angular MCP server from Chapter 1 earns its place. lets the agent execute the suite and read the failures: Without that last clause you get the classic failure: the test fails, the model "fixes" it by loosening the assertion, and you end up with a green suite that checks nothing. What the generated tests should look like Note — that is how you set a signal input in a test. A model working from older training data will often write , which does not compile against a signal input. If it does, that is your signal that was not consulted. Also note: Vitest is the default runner for new Angular 22 projects, via the builder. Karma still works and is still supported, but is deprecated. The guardrail that matters most Here is the failure mode specific to this chapter. You ask for tests. A test fails, because the component genuinely does not handle something. The model's most natural repair is to change the component — and the smallest change that makes a test pass is very often a new input. Every one of those is now a permanent promise to twenty consumer applications, added by a process that was supposed to be writing tests. Three controls stop it: 1. Say it in the rules file. Add to your : 2. Snapshot the public API and diff it in CI. Chapter 8 builds this properly with . The cheap version is enough to start: 3. Put behind CODEOWNERS , as in Chapter 2. Internals are reversible; exported inputs are not. Accessibility falls out for free Because Chapter 3's components use and real roles, the tests a model writes against them are naturally resilient: Nothing here breaks when you rename a CSS class. Compare with the test the same model writes against a implementation: it has nothing to query but , and it breaks the first time a designer reorders the options. This is a general property worth noticing. The things that make a component accessible are the same things that make it testable , because both are asking the same question: can something other than a sighted mouse user tell what this is and operate it? "But I thought AI was bad at writing tests" Plenty of teams have tried this, got a suite full of tests that assert , and concluded the technology is not there. That experience is real and the conclusion is understandable — it is just measuring the wrong thing. A test suite is a description of what a component is supposed to do. If that description does not exist anywhere, the model has to invent one, and inventing product requirements is genuinely something it is bad at. Watch where the quality actually comes from. Same model, same prompt, two components: Loose contract (4 booleans, inputs, ) Strict contract (union, literal types, ) --- --- --- Cases it identifies 4 of 16, arbitrarily chosen 4 of 4 Tests asserting impossible states 2–3 0 Selectors used CSS classes Roles and accessible names Tests that survive a CSS rename 40% 100% Keyboard behaviour covered None — there is none to cover Yes API accidentally widened Common Blocked by lint and CODEOWNERS Nothing abou…