I'm writing tests for the config file of a Flask application. To make sure the env variables set in the system do not influence the results of the test I'm using pytest's monkeypatch to create predictable test outcomes.
I'm testing the config file once in a 'clean' state with a fixture with no env variables set and once in a 'fake' config, with a fixture where I let monkeypatch set the variables before running the test.
Both fixtures set env variables and then import the config object before passing it on to the test function.
When the config object is loaded at the head of the document instead of inside the fixtures, both fixtures use a version based on the actual system env variables.
It seems like the second fixture does not import the config object, but reuses the one created by the cleanConfig fixture. How can I force the fixture to reimport the config object?
test_config.py:
import pytest
from config import config
class TestConfigSettings(object):
@pytest.fixture(scope='function')
def cleanConfig(config_name, monkeypatch):
def makeCleanConfig(config_name):
monkeypatch.delenv('SECRET_KEY', raising=False)
monkeypatch.delenv('DEV_DATABASE_URL', raising=False)
from config import config
configObject = config[config_name]
return configObject
return makeCleanConfig
@pytest.fixture(scope='function')
def fakeEnvConfig(config_name, monkeypatch):
def makeFakeEnvConfig(config_name):
monkeypatch.setenv('SECRET_KEY', 'fake difficult string')
monkeypatch.setenv('DEV_DATABASE_URL', 'postgresql://fake:5432/fakeDevUrl')
from config import config
configObject = config[config_name]
return configObject
return makeFakeEnvConfig
def test_configObject_withDevelopmentConfig_containsCorrectSettings(self, cleanConfig):
configObject = cleanConfig('development')
assert configObject.SECRET_KEY == 'hard to guess string'
assert configObject.DEBUG == True
assert configObject.SQLALCHEMY_DATABASE_URI == None
def test_configObject_withDevelopmentConfigAndEnvSet_copiesEnvSettings(self, fakeEnvConfig):
configObject = fakeEnvConfig('development')
assert configObject.SECRET_KEY == 'fake difficult string'
assert configObject.SQLALCHEMY_DATABASE_URI == 'postgresql://fake:5432/fakeDevUrl'
Config.py:
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL')
config = {
'default': DevelopmentConfig,
'development': DevelopmentConfig,
...
}
I finally found a solution for my problem. By using the reload() function, you can import a module again after changing the content (the loaded env variables in this case). To be able to use it I had to change the import to the config module instead of the config dictionary I was importing before, since the reload() object only works on modules. The new code:
import pytest
from importlib import reload
import config
class TestConfigSettings(object):
@pytest.fixture(scope='function')
def cleanConfig(config_name, monkeypatch):
def makeCleanConfig(config_name):
monkeypatch.delenv('SECRET_KEY', raising=False)
monkeypatch.delenv('DEV_DATABASE_URL', raising=False)
reload(config)
configObject = config.config[config_name]
return configObject
return makeCleanConfig
@pytest.fixture(scope='function')
def fakeEnvConfig(config_name, monkeypatch):
def makeFakeEnvConfig(config_name):
monkeypatch.setenv('SECRET_KEY', 'fake difficult string')
monkeypatch.setenv('DEV_DATABASE_URL', 'postgresql://fake:5432/fakeDevUrl')
reload(config)
configObject = config.config[config_name]
return configObject
return makeFakeEnvConfig