Answers · Keys & Services

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.

Related questions

Do I need API keys to run a Rails starter kit locally?

No. Billing, email, bot checking and error reporting each sit behind an adapter that returns a real client when its credential is present and a deterministic local fake when it is not. A fresh clone runs bin/setup and bin/dev with no keys, and checkout, sign-in and gated features all work end to end.

How do I add sign in with Google to a Rails app?

Create an OAuth 2.0 client in Google Cloud Console, add your callback URL as an authorized redirect URI, and set the client id and secret. One Shot already has the OmniAuth callback route and the user upsert, so no code changes. Google's host also has to be in the content security policy's form-action list.

How do I preview emails in development without sending them?

One Shot uses letter_opener in development, so every deliver_later opens the rendered email in a browser tab instead of sending it. No key and no account are needed. That is how you read a sign-in code locally, and it means a seed script cannot accidentally email a real address.