simulating a third party you don't control

You can't point a test environment at a third party's API. Not because they'll stop you (usually they won't), but because every CI run, every branch, every retry loop somebody left running over the weekend is traffic hitting another company's production systems. They never agreed to host your test suite. A test environment left pointed at a vendor is a noisy neighbor, and eventually you're the reason someone else's on-call got paged.

So we built a simulation service that stood in for the integration. Everything below production talked to the simulator, and it did a decent impression of the real API.

That solves the obvious problem and quietly creates a second one. You've now got two places things can drift apart instead of one: our services against the simulator, and the simulator against whatever the third party is actually doing this week.

Contract testing fixes the first one

The nice thing about a simulator you built is that you own both sides of that boundary, even though you don't own the integration it's imitating. Consumers declare what they expect, the simulator verifies it can produce that, and both sides get checked independently in CI. If someone changes the simulator's response shape, the consumer contracts fail immediately instead of six weeks later.

This is the part that worked well, and it's worth doing even if you stop there. It turns the simulator from a pile of fixtures somebody wrote once into something with a defined job that gets verified on every build.

It does nothing for the second one

Here's the part I'd want to tell anyone setting this up: a contract test proves your consumers and your simulator agree with each other. It proves nothing about whether either of them agrees with reality.

You can have a fully green contract suite sitting on top of a simulator that is confidently, specifically wrong: returning a field the vendor renamed, a 400 where they now send a 422, results sorted the way they used to sort them. Everything passes. The contract is satisfied. The contract was just never the source of truth; the vendor was, and nobody asked them.

The only thing that helps is checking against the real thing on some interval. Which puts you right back where you started: you built the simulator specifically so you'd stop generating traffic against them.

The way out is that the reality check is tiny by design. You are not running your suite against the vendor. It's one scheduled job, a handful of requests, diffing the response shapes against what your simulator claims they are, and cleaning up after itself, because those calls make real records on someone else's system and leaving them there is its own kind of rude.

That's a completely different footprint from what you were avoiding. The thing that makes you a noisy neighbor is dozens of concurrent pipelines firing all day, every day, forever. A dozen calls a week is roughly what one impatient user does, and it's enough to notice a renamed field before it reaches production.

That's the whole trade: keep the volume off their infrastructure, keep a thin wire connected to it so you find out when the ground moves.

Keep the simulator boring

The other trap is that a simulator like this wants to grow. It starts as canned responses, then it needs state so a POST followed by a GET makes sense, then it needs validation rules so the error cases are testable, and at some point you're maintaining a reimplementation of somebody else's product with none of their documentation.

Every rule you add is another thing that can be wrong in a way contract tests won't catch. Simulate enough to unblock the code under test and let the real integration tests cover the rest.

Two boundaries, two different tools. Contract tests keep your side honest. Only the vendor can keep the simulator honest, so make sure you're still asking them.