Q22 of 37 · API testing

How do you organise environment variables and secrets across local/staging/prod API tests?

API testingMidapiconfigsecretsenvironments

Short answer

Short answer: Layered config: defaults in code, per-environment files (.env.local, .env.staging) for non-secrets, secrets in a manager (1Password, Vault, AWS Secrets Manager). Never commit secrets. Inject via env vars at runtime; test code reads `process.env.X` only.

Detail

Test environments diverge: local hits localhost or Docker; staging hits cloud staging; CI hits whichever it's targeting. Config sprawl is one of the most common reasons API test suites become unmaintainable.

The layered model:

Layer 1 — Defaults in code:

const config = {
  baseUrl: process.env.API_BASE_URL ?? 'http://localhost:3000',
  timeout: Number(process.env.API_TIMEOUT ?? 30_000),
};

Layer 2 — Per-environment, non-secret files. Checked into git:

# .env.staging
API_BASE_URL=https://staging-api.example.com
API_TIMEOUT=60000

Test runner picks the file based on NODE_ENV or an explicit flag.

Layer 3 — Secrets, never in git. Pulled at runtime from:

  • 1Password for local dev — op run --env-file=.env.local.tmpl -- npm test.
  • HashiCorp Vault for self-hosted CI.
  • AWS Secrets Manager / GCP Secret Manager for cloud CI.
  • GitHub Actions secrets for GHA pipelines (encrypted, scoped per env).

The .env.example file commits placeholders so engineers know what to set:

# .env.example
API_BASE_URL=
API_TOKEN=        # Get from 1Password vault "QA secrets"

The principles:

1. Secrets never travel through git, ever. Use .env.local (gitignored) or a secret manager. git-secrets and pre-commit hooks catch accidental commits.

2. Test code reads only process.env.X. Never hardcode endpoints, tokens, or paths. The runtime decides.

3. One source of truth per environment. Don't have credentials in .env and in CI secrets — drift is guaranteed. Pull from one, copy from there.

4. Environment names match the test code. local, staging, prod is enough; don't create dev1, dev2, testenv-3 unless they correspond to real isolated environments.

5. Tests fail loudly on missing config:

if (!process.env.API_TOKEN) {
  throw new Error('API_TOKEN required — set in .env.local');
}

Better than tests passing because they default to localhost when staging credentials are missing.

Anti-patterns:

  • Hardcoded staging URL in 12 different test files. Refactor to a single config.ts.
  • "Test secrets" committed because "they're just test accounts." If they grant any access, they're real secrets.
  • Running prod-targeted tests by accident. Add an explicit safety check: refuse to delete in prod even if config points there.

For data-intensive secrets (test card numbers, OAuth credentials), keep them in the secret manager too. Rotation is easier and the secret manager logs access.

// WHAT INTERVIEWERS LOOK FOR

Layered model (defaults → env files → secret manager), .env.example for discoverability, explicit safety checks for prod, and the discipline of one source of truth per environment.

// COMMON PITFALL

Committing 'test' secrets to .env or CI YAML 'because they're not production.' Test accounts often grant real access (Stripe test keys, sandbox APIs with real costs); treat them as secrets.