DEV Community

Dmitrii Doroshev
Dmitrii Doroshev

Posted on • Originally published at doroshev.substack.com

Why Django's override_settings Sometimes Fails (and How reload + patch Saved Me)

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" 
Enter fullscreen mode Exit fullscreen mode

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" 
Enter fullscreen mode Exit fullscreen mode

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!" 
Enter fullscreen mode Exit fullscreen mode

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!" 
Enter fullscreen mode Exit fullscreen mode

Why This Works

  • importlib.reload(dragon) forces a fresh import of dragon, rebuilding DRAGON with the overridden settings;
  • dragon.DRAGON is updated in the scope of the test only, i.e. mario module still has the stale version of DRAGON;
  • patch(...) solves this problem by swapping the old DRAGON in mario 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() and patch() 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.