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
conf.py 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
test_conf.py 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 @pytest.fixture 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
conf.py 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?
@pytest.fixture def patched_env(monkeypatch): monkeypatch.setenv("SETTING_A", "0") monkeypatch.setenv("SETTING_B", "0") reload(conf)
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
@pytest.fixture(autouse=True) def reload_conf_module(): reload(conf)
This ensures a clean global state before any test is run, so that all tests pass.