pytest modules using os.environ - How do I test it correctly?

16.9k views Asked by At

currently I am writing some Webapp, but this time I want to learn how to write proper tests for it (using pytest) :)

A very common pattern I often see is to make the default configuration changeable using environment variables. Currently I am struggling how to test this properly.

I've prepared some demo:

./app
./app/conf.py
./conftest.py
./run.py
./tests
./tests/test_demo.py

My ./app/conf.py looks like this:

from os import environ

DEMO = environ.get('DEMO', 'demo')
TEST = environ.get('TEST', 'test')

Launching the ./run.py shows that the settings are indeed changeable:

from os import environ

environ['DEMO'] = 'not a demo'
environ['TEST'] = 'untested'

from app import conf

if __name__ == '__main__':

    print(conf.DEMO)
    print(conf.TEST)

It prints out not a demo and untested - as expected. Great. (Note that I set the environment variables before importing conf).

Now to the tests: The ./conftest.py is currently empty it just helps pytest to locate the modules inside the app folder.

The ./tests/test_demo.py contains the following:

def test_conf_defaults():
    from app import conf

    assert conf.DEMO == 'demo'
    assert conf.TEST == 'test'


def test_conf_changed(monkeypatch):
    monkeypatch.setenv('DEMO', 'tested demo')
    monkeypatch.setenv('TEST', 'demo test')

    from app import conf

    assert conf.DEMO == 'tested demo'
    assert conf.TEST == 'demo test'

    monkeypatch.undo()

If I run pytest now, test_conf_changed fails with 'demo' == 'tested demo' -> the monkeypatch function did not patch the environment.

If I swap both testing functions (so test_conf_changed runs first), the test_conf_defaults will fail with 'tested demo' == 'demo'.

How I interpret it, is - the first time conf gets imported it sticks there with it's initial settings..

How can I tell pytest to completely reimport conf each test function, after setting up the environment variables?

I am stuck there for two days now - and slowly I doubt if testing is worth the hassle - please prove me wrong :)

2

There are 2 answers

0
spky On

Thanks for the hint, Evert (variables inside the conf module are set inside the global namespace, they stick around) - I think I got it now.

To test my code I have to explicitly reimport conf after setting the environment variables. Changing the code in ./tests/test_demo.py to this does the trick:

from importlib import reload

from app import conf


def test_conf_changed(monkeypatch):
    monkeypatch.setenv('DEMO', 'tested demo')
    monkeypatch.setenv('TEST', 'demo test')

    reload(conf)

    assert conf.DEMO == 'tested demo'
    assert conf.TEST == 'demo test'


def test_conf_defaults():

    reload(conf)

    assert conf.DEMO == 'demo'
    assert conf.TEST == 'test'

Thank you.

1
rnickle On

I encountered a variation of the same issue while using pytest and importlib to validate imports. The imported modules have the option to override certain settings with environment variables. After patching the environment variable with monkeypatch, it is necessary to call importlib.reload against the imported module object in order for the patched variables to be evaluated since they were originally evaluated before the monkeypatch.

Adding this variation of spky's answer with an importlib example to increase the answer scope to cover cases for those working with importlib.

import importlib

def test_env_override(monkeypatch):
    monkeypatch.setenv('APP_ROOT', '/tmp')
    _temp = importlib.import_module('appconfig', 'APP_ROOT')
    importlib.reload(_temp)
    assert getattr(_temp, 'APP_ROOT') == '/tmp'