DEV Community

Nico Reyes
Nico Reyes

Posted on

My Python tests passed. Production still broke.

Deployed my Python API to production last Friday. All tests green. Felt good about it.

Monday morning support messages me. API returning 500 errors for about 30% of requests. Tests still passing locally.

Great.

What broke

My code parsed JSON from an external API:

import requests

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    data = response.json()
    return {
        'name': data['profile']['name'],
        'email': data['contact']['email'],
        'age': data['profile']['age']
    }
Enter fullscreen mode Exit fullscreen mode

Tests mocked the API response with perfect JSON. Every field present. Proper nesting. Exactly what I expected and nothing like what actually came back half the time.

Production API sometimes returned incomplete data. Missing profile object. Missing contact email. Still valid JSON, just structured differently.

My tests never checked for this because I wrote happy path mocks like an idiot.

def test_get_user_data():
    mock_response = {
        'profile': {'name': 'John', 'age': 30},
        'contact': {'email': 'john@example.com'}
    }
    # This never happens in real life
    result = get_user_data(123)
    assert result['name'] == 'John'
Enter fullscreen mode Exit fullscreen mode

Real API had:

  • New users missing profile data
  • Deactivated accounts missing contact info
  • International users with different field names
  • Probably other stuff I still haven't found

My perfect mock data taught me nothing.

What actually worked

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    data = response.json()

    # Get with defaults instead of assuming structure
    profile = data.get('profile', {})
    contact = data.get('contact', {})

    return {
        'name': profile.get('name', 'Unknown'),
        'email': contact.get('email', None),
        'age': profile.get('age', None)
    }
Enter fullscreen mode Exit fullscreen mode

Defensive access patterns everywhere. Also added tests with missing fields:

def test_get_user_data_missing_profile():
    mock_response = {'contact': {'email': 'test@example.com'}}
    # Entire profile object gone
    result = get_user_data(123)
    assert result['name'] == 'Unknown'

def test_get_user_data_empty_response():
    mock_response = {}
    # API returned but gave us nothing
    result = get_user_data(123)
    assert result['name'] == 'Unknown'
Enter fullscreen mode Exit fullscreen mode

Should've written these first honestly. Testing only perfect scenarios is like practicing catch with yourself.

Still figuring out what other edge cases exist. Prod keeps finding them for me.

Top comments (0)