Challenge

I was doing a little refactor for a codebase I worked in. The idea is to collect all free form environment variables into one class to have a centralized place to manage them all to simplify maintanence work.

It was a simple task on the surface, but I ran into problems when structuring my unittest.
The challange was realted to how Python handles class static variables. Python Class executes once when it is first loaded and then cached. Consecutive calls uses the cashed value to avoid duplicate work.

For example if we have the following code:

# project_root/config.py
Class MyDemoClass:
    NAMESPACE = os.getenv("NAMESPACE)
    DEV_ENV = os.getenv("DEV_ENV")

The MyDemoClass.NAMESPACE and MyDemoClass.DEV_ENV are class static fields that all instances of MyDemoClass shares.

Solution

To write proper unittest, I need to have the power to force reload static fields for a class. Before my test run, I supplied mocked os.environ. However because Python runtime handles when the MyDemoClass is loaded, by the time I supplied the mock in my unittest, the class may have already be loaded and invoked all the os.getenv too early. So the workaround I found is to force reload the MyDemoClass module after I have mocked out the os.environ sys varaible.

Like this:

# project_root/tests/test_mydemoclass.py
import unittest
from unittest.mock import patch
import config
from importlib import reload 

Class TestMyDemoClass(unittest.Testcase):
    # https://stackoverflow.com/questions/437589/how-do-i-unload-reload-a-python-module

    @pathc.dict('os.environ', {'NAMESPACE' : 'my_test_namespace', 'DEV_ENV' : 'my_test_dev_env'})
    def test_load_env(self):
        reload(config)
        from config import MyDemoClass
        self.assertEqual("my_test_namespace", MyDemoClass.NAMESPACE)

Note that reload is the tool I used to force reload a module (so that the os.getenv uses the mock value), and the from config import MyDemoClass allows me to use the just freshly reloaded MyDemoClass.