Now look at their tests. The payments team has a suite. The checkout team has a suite. When the checkout team needs to prove that a declined card stops an order, they can't reuse a single line of what the payments team already wrote. Different framework, different conventions, different mocks. So they build it again, from scratch, in their own style. One integration, tested twice — and neither test knows the other exists.
That's the asymmetry I can't unsee: we gave our services a shared contract, and we gave our tests nothing.
The code has a standard. The tests don't.
A contract is what lets independent teams build parts that fit. It's a shared, agreed, versioned artefact — an OpenAPI spec, an interface, a schema — and everyone aligns to it. That's the entire reason a system assembled by six teams works at all.
Tests have no equivalent. Every team writes them their own way: their framework, their naming, their fixtures, their idea of what "component" versus "integration" even means. There's no shared artefact for a test to align to, so there's nothing for another team to pick up. The knowledge of how to exercise the payments service exists — it's just locked inside one team's suite, in a shape no one else can consume.
We standardised the thing the machines talk over, and left the thing the humans maintain completely unstandardised.
This is why component tests die at integration
The asymmetry has a second cost, and it's the one that burns a sprint.
Each team writes its component tests in isolation, against its own mocks. Those mocks encode assumptions — payments returns 201 with an orderId, a decline is a 402 — and they pass, beautifully, because a mock always agrees with the assumptions that built it.
Then you wire the real services together for integration or E2E, and the assumptions collide. My mock said 402; your service says 409. My test never exercised the retry your service expects. Neither suite was built from a shared source of truth, so the moment they meet, they disagree — and you rewrite both to reconcile them. You don't have one test that runs at three levels. You have three tests that drift, maintained in triplicate, reconciled by hand every release.
"Just reuse the code" — and "just ask AI" — don't fix this
Two answers usually come up. Neither closes the gap.
Reuse the code. You can't, cleanly. The component suite is welded to its mocks, the E2E suite to a live environment, and each grew its own framework. There's no seam to lift a test out of one and drop it into another.
Let AI generate them. AI is genuinely good at drafting a test — but drafting faster doesn't make the output shared. Point three teams at the same spec and you get three plausible, differently-shaped suites that still can't be composed, plus a new problem: ask twice and the verdict may differ. You've automated the production of silos. The bottleneck was never writing tests. It was that there's nothing for the tests to align to.
The shift: give your tests a contract too
The fix is the same move we already made for code — a shared, versioned, installable artefact that teams align to and pass between each other. For tests, that artefact is a plugin: one system's test knowledge in one place — the endpoints, the request shapes, the expected responses, the negative paths — kept in git and versioned like a library.
The payments team owns and publishes the payments plugin. Any other team installs it:
# reuse the payments team's test knowledge instead of rebuilding it
syz install github.com/syzs-code/payments-plugin.git@v1.2.0 --plugin payments
The checkout team's scenario is then composed from the payments team's building blocks — the same steps the payments team already proved, wired into a checkout flow:
flow:
- type: send
agent: payments.step_send_order # the payments team's step, reused
inputs: { userId: "u-001", amount: 100, currency: "GBP" }
- type: validate
agent: payments.validate_order_response
inputs: { expected_status: 201 }
And because the flow is separate from the environment it runs against, the same scenario runs at every level — you change one flag, not the test:
syz run --suite checkout.yaml --env local # component — against a mock
syz run --suite checkout.yaml --env staging # integration — against real staging
Same building blocks, shared across teams, run at every level. The 402-vs-409 disagreement can't happen, because both teams are asserting against the same published knowledge, not two private copies of it. Execution stays deterministic — no AI in the assertions or the ledger — so the verdict is the same on your laptop, in CI, and on a teammate's machine.
What actually changes
- Tests compose like your services do. A scenario is assembled from plugins other teams own, not re-derived from scratch.
- Write once, run at every level. Component, integration, E2E — one flow, switch the environment.
- The knowledge outlives the author. It's a versioned artefact in git, reviewed in PRs, owned by the team — not trapped in one person's suite.
- A breaking change propagates by version bump, not by a broadcast email and six teams patching in parallel.
You already do this for the code the machines run. This is the same discipline, applied to the tests the people maintain.
Try it — and come build with us if it fits
The fastest way to know whether this fits your teams is to see it work. The tutorial walks the whole path hands-on, and the demo workspace at github.com/syzs-code/syz-cli-demo is a complete, runnable example you can clone and follow.
npm install -g syzs-cli
syz init
Generate a plugin from a spec you already have, and see whether your tests finally line up across teams and levels. And if you'd like to go further than trying it — to work with us, help shape where this goes, and be part of the journey — email support syz.sh. The door is always open.