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
, rebuildingDRAGON
with the overridden settings; -
dragon.DRAGON
is updated in the scope of the test only, i.e.mario
module still has the stale version ofDRAGON;
-
patch(...)
solves this problem by swapping the oldDRAGON
inmario
with 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.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.