Monkeypatching Module Globals

This is an issue I came across when trying to write a test to ensure a configuration is correctly read from a number of environment variables: It can be tricky when module global variables are part of the setup. I am using pytest here, but the problem would happen with any method that patches the environment. Let’s say I have a module like this:

import os

SETTING_A = int(os.getenv("SETTING_A", "1"))
SETTING_B = int(os.getenv("SETTING_B", "1"))

Say we want to write two tests in to check that the value is parsed correctly from the environment, one test with the default and one with a monkeypatched environment:

import pytest
import conf

def patched_env(monkeypatch):
    monkeypatch.setenv("SETTING_A", "0")
    monkeypatch.setenv("SETTING_B", "0")

def test_conf_with_patched_env(patched_env):
    assert conf.SETTING_A == 0
    assert conf.SETTING_B == 0

def test_conf_with_default_env():
    assert conf.SETTING_A == 1
    assert conf.SETTING_B == 1

What will happen? The test_conf_with_patched_env() will fail, since the values inside are already set on import time and not changed by the monkeypatch.setenv() calls. No problem, we can just reload the module after monkeypatching to refresh those values, can’t we?

def patched_env(monkeypatch):
    monkeypatch.setenv("SETTING_A", "0")
    monkeypatch.setenv("SETTING_B", "0")

Now test_conf_with_patched_env() will pass, the test using the defaults however will fail (note that this depends on the test order, so it might have worked when swapping the test function definitions, but the problem still exists). The monkeypatch changes are kept for all following tests, since again the global module state has been changed and is not reset for every test. We could add a reload(conf) in every test that needs it, or write a fixture for it. In my case I worked around this problem with a function scoped autouse fixture inside the directory’s

def reload_conf_module():

This ensures a clean global state before any test is run, so that all tests pass.

Yes alright, it is still a hack. A better approach would be to avoid using global module variables in this case or not storing the values at all and instead use getter functions.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.