How do I add a third-party API without breaking my test suite?
Wrap it in an adapter that returns a real client when its credential is present and a deterministic fake when it is not, the way app/adapters/billing.rb does. The test suite runs against the fake, so there are no network calls, no API keys in CI and no test that fails because someone else's service is down.
The shape
Slack.for(account).post(message)
One entry point. Behind it, two implementations with the same methods, chosen by whether the
credential is set. Nothing that calls it knows or cares which it got. app/adapters/billing.rb is
the reference version, and app/adapters/bot_check.rb is the short one worth reading first.
The add-integration skill generates exactly this, including the fake and the spec. Use it rather
than writing the pattern from memory, because the part people get wrong is making the fake
deterministic rather than a stub that returns nil.
Why not just stub it in the specs
Stubbing works and it rots. Each spec stubs the shape it happens to need, the stubs drift from the real client's interface, and the suite goes green against an API that changed six months ago. A single fake is one place that has to stay honest, and it is exercised by every spec that touches the feature.
It also means development works with no account at all, which is a different and larger benefit: a new clone can run the whole flow before anyone has requested a key.
Never call the API from a controller or a model
That is the rule the pattern exists to enforce. A third-party call inside a controller cannot be faked, so it has to be stubbed, which means it cannot be exercised, which means it breaks quietly. The seam is the point.
Make the fake behave, not just respond
A fake that returns true for everything passes tests and teaches you nothing. Model the failure
cases you actually care about: a rate limit, a missing record, an invalid token. Those are the
branches your error handling exists for and the ones that never run otherwise.
Verify
bin/check
The suite should pass with no credentials set anywhere, on a machine with no network access to the provider. If it does not, something is calling the real service, and that is the thing to find.
Then test against the real one, once
Before launch, run the flow with a real key in a test or sandbox mode. The fake proves your code is consistent; only the real service proves your assumptions about it were right.