How to Mock Environment Variables in Python for 12-Factor Config

Read 12-factor config from env vars and test/mock them with unittest.mock.patch.dict without touching the real environment.

Medium Python 3.9+ Aug 9, 2026 Auth & security at scale 13 views 0 copies

Python code

42 lines
Python 3.9+
import os
import json
from unittest.mock import patch

def load_config(env_prefix="APP"):
    """Read 12-factor style config from env vars"""
    required = ["DATABASE_URL", "API_KEY"]
    optional = {"PORT": "8080", "DEBUG": "false"}
    
    config = {}
    for key in required:
        full_key = f"{env_prefix}_{key}"
        value = os.environ.get(full_key)
        if value is None:
            raise ValueError(f"Missing required env var: {full_key}")
        config[key] = value
    
    for key, default in optional.items():
        full_key = f"{env_prefix}_{key}"
        config[key] = os.environ.get(full_key, default)
    
    return config

if __name__ == "__main__":
    # Mock environment vars without touching real environment
    mock_env = {
        "APP_DATABASE_URL": "postgres://localhost/mydb",
        "APP_API_KEY": "secret-key-123",
        "APP_DEBUG": "true",
        "APP_PORT": "3000"
    }
    
    with patch.dict(os.environ, mock_env):
        config = load_config()
        print(json.dumps(config, indent=2))
    
    # Without mock, should fail on missing required vars
    try:
        config = load_config()
        print(config)
    except ValueError as e:
        print(f"Error: {e}")

Output

stdout
{
  "DATABASE_URL": "postgres://localhost/mydb",
  "API_KEY": "secret-key-123",
  "PORT": "3000",
  "DEBUG": "true"
}
Error: Missing required env var: APP_DATABASE_URL

How it works

The load_config function reads required and optional keys from os.environ using an env_prefix. For required keys, it raises ValueError if missing. Optional keys fall back to defaults. patch.dict(os.environ, mock_env) temporarily replaces the environment dictionary for the with block, so the mock applies only within that scope and the real environment is untouched. This is the idiomatic way to simulate env vars in tests without monkeypatching os.environ globally.

Common mistakes

  • Forgetting to include all required keys in the mock dict, causing ValueError during tests.
  • Modifying `os.environ` directly instead of using patch.dict, which can leak state between tests.
  • Assuming `os.environ.get` returns None for missing keys when using default parameters incorrectly.

Variations

  1. Use `os.getenv` with a default for optional keys to make the code more concise.
  2. Use a dataclass or TypedDict to type the config structure for better IDE support.

Real-world use cases

  • Testing 12-factor config loading in CI pipelines without hardcoding secrets in test files.
  • Simulating different deployment environments (dev, staging, prod) in automated integration tests.
  • Rotating secrets and validating that services fail fast when required env vars are absent.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Auth & security at scale

Related tutorials and quizzes for this topic.