Why Django's override_settings Sometimes Fails (and How reload + patch Saved Me)
A real-world case of fighting against imported globals, stale settings, and brittle Django tests - and winning
Sometimes @override_settings just doesn’t cut it.
I ran into a nasty issue while testing a Django module that relies on global state initialized during import. The usual test approach didn’t work. Here’s what happened and how I solved it.
The Setup
We had a module that builds a global dictionary from Django settings at import time. Let’s call it dragon.py, which takes settings.PUT_EGGS, which is False by default:
from django.conf import settings
DRAGON = {}
...
if settings.PUT_EGGS:
DRAGON["eggs"] = "spam"Another module uses DRAGON for core logic, e.g. mario.py:
from myproject.dragon import DRAGON
def find_eggs():
if "eggs" in DRAGON:
return "Found eggs!"
return "Eggs not found"Now I wanted to write a test that tweaks DRAGON and expects the logic to behave differently. Easy, right?
@override_settings(PUT_EGGS=True)
def test_find_eggs():
assert find_eggs() == "Found eggs!"Wrong. The test failed.
The Problem
override_settings works, but only for code that reads settings at runtime.
In my case, DRAGON was already built at import time, before the override kicked in. So it used the old value of PUT_EGGS, no matter what I did in the test.
This is the classic trap of global state baked during import. Welcome to pain town.
The Fix: reload + patch
Here's how I got out:
import importlib
from django.test import override_settings
from unittest.mock import patch
from myproject.mario import find_eggs
@override_settings(PUT_EGGS=True)
def test_find_eggs():
# Reload the dragon module so DRAGON is rebuilt
# with updated settings
from myproject import dragon
new_dragon = importlib.reload(dragon)
# Patch the logic module to use the reloaded DRAGON
with patch('myproject.mario.DRAGON', new_dragon.DRAGON):
result = find_eggs()
assert result == "Found eggs!"Why This Works
importlib.reload(dragon)forces a fresh import ofdragon, rebuildingDRAGONwith the overridden settings;dragon.DRAGONis updated in the scope of the test only, i.e.mariomodule still has the stale version ofDRAGON;patch(...)solves this problem by swapping the oldDRAGONinmariowith the freshly rebuilt one.
This is surgical. Ugly, but effective.
Lessons Learned
Avoid putting non-trivial logic at module scope, especially if it depends on Django settings. Wrap it in a function or lazy loader.
If you're stuck with global state,
reload()andpatch()give you a way out - just be careful about cascading dependencies.
If you’ve ever had a test mysteriously fail after overriding settings, this might be why.