skip to Main Content

I wrote in python a config module for getting values from the env. The env is set by the docker-compose file. I want to test the config module, so i want to unset the variables in the test before importing the module.

Code:
test_config.py:

import os
from unittest import TestCase

def test_without_env():
    os.unsetenv('GITLAB_URL')
    os.unsetenv('GITLAB_PROJECT_ID')
    os.unsetenv('GITLAB_API_TOKEN')
    
    os.unsetenv('JIRA_API_TOKEN')
    os.unsetenv('JIRA_BOARD_ID')
    os.unsetenv('JIRA_URL')

    os.unsetenv('ROCKET_CHAT_API_TOKEN')
    os.unsetenv('ROCKET_CHAT_USER_ID')
    os.unsetenv('ROCKET_CHAT_URL')

    import src.config as conf

    assert conf.GITLAB_URL == 'URL'
    assert conf.GITLAB_PROJECT_ID == '1234'
    assert conf.GITLAB_API_TOKEN == 'TOKEN'

    assert conf.JIRA_API_TOKEN == 'TOKEN'
    assert conf.JIRA_BOARD_ID == '1234'
    assert conf.JIRA_URL == 'URL'

    assert conf.ROCKET_CHAT_API_TOKEN == 'TOKEN'
    assert conf.ROCKET_CHAT_URL == 'URL'
    assert conf.ROCKET_CHAT_USER_ID == 'USER_ID'

config.py:

import os

# GitLab
if 'GITLAB_URL' in os.environ: GITLAB_URL = os.environ['GITLAB_URL']
else: GITLAB_URL = 'URL'

if 'GITLAB_PROJECT_ID' in os.environ: GITLAB_PROJECT_ID = os.environ['GITLAB_PROJECT_ID']
else: GITLAB_PROJECT_ID = 1234

if 'GITLAB_API_TOKEN' in os.environ: GITLAB_API_TOKEN = os.environ['GITLAB_API_TOKEN']
else: GITLAB_API_TOKEN = 'TOKEN'

# Jira
if 'JIRA_URL' in os.environ: JIRA_URL = os.environ['JIRA_URL']
else: JIRA_URL = 'URL'

if 'JIRA_BOARD_ID' in os.environ: JIRA_BOARD_ID = os.environ['JIRA_BOARD_ID']
else: JIRA_BOARD_ID = 1234

if 'JIRA_API_TOKEN' in os.environ: JIRA_API_TOKEN = os.environ['JIRA_API_TOKEN']
else: JIRA_API_TOKEN = 'TOKEN'

# RocketChat
if 'ROCKET_CHAT_API_TOKEN' in os.environ: ROCKET_CHAT_API_TOKEN = os.environ['ROCKET_CHAT_API_TOKEN']
else: ROCKET_CHAT_API_TOKEN = 'TOKEN'

if 'ROCKET_CHAT_URL' in os.environ: ROCKET_CHAT_URL = os.environ['ROCKET_CHAT_URL']
else: ROCKET_CHAT_URL = 'URL'

if 'ROCKET_CHAT_USER_ID' in os.environ: ROCKET_CHAT_USER_ID = os.environ['ROCKET_CHAT_USER_ID']
else: ROCKET_CHAT_USER_ID = 'USER_ID'

(Values from variables and assert are changed, because of security 😀 )

Am I doing it right? Or what can i do? I think that docker is rewrite the env if it changed, just a guess…

Greetings,
DasMoorhuhn

It does not change, I tried os.unsetenv('VAR'), os.system('unset VAR') and del os.environ['VAR'].

2

Answers


  1. You are doing it right by using os.unsetenv(‘VAR’) or del os.environ[‘VAR’], but the main issue you’re encountering is due to the fact that environment variables are loaded when a module is imported in Python. Once the module has been imported, any changes you make to the environment won’t affect the values that were set at the time of the import.

    You are trying to unset the environment variables and then import the module. However, if your module is imported somewhere before the unsetting, the environment variables are already set for that instance of the module. That’s why unsetting them doesn’t seem to have any effect.

    You can get around this problem by reloading the module after you’ve changed the environment. Python provides a built-in function, importlib.reload(), which you can use to reload a previously imported module. The argument must be a module object, so you must import the module first, then delete the environment variables, and then reload the module.

    Here is how you could rewrite your test:

    import os
    from unittest import TestCase
    import importlib
    
    def test_without_env():
        import src.config as conf
    
        os.environ.pop('GITLAB_URL', None)
        os.environ.pop('GITLAB_PROJECT_ID', None)
        os.environ.pop('GITLAB_API_TOKEN', None)
        
        os.environ.pop('JIRA_API_TOKEN', None)
        os.environ.pop('JIRA_BOARD_ID', None)
        os.environ.pop('JIRA_URL', None)
    
        os.environ.pop('ROCKET_CHAT_API_TOKEN', None)
        os.environ.pop('ROCKET_CHAT_USER_ID', None)
        os.environ.pop('ROCKET_CHAT_URL', None)
    
        importlib.reload(conf)
    
        assert conf.GITLAB_URL == 'URL'
        assert conf.GITLAB_PROJECT_ID == '1234'
        assert conf.GITLAB_API_TOKEN == 'TOKEN'
    
        assert conf.JIRA_API_TOKEN == 'TOKEN'
        assert conf.JIRA_BOARD_ID == '1234'
        assert conf.JIRA_URL == 'URL'
    
        assert conf.ROCKET_CHAT_API_TOKEN == 'TOKEN'
        assert conf.ROCKET_CHAT_URL == 'URL'
        assert conf.ROCKET_CHAT_USER_ID == 'USER_ID'
    

    In this code, os.environ.pop(‘VAR’, None) is used to remove the environment variable. This method removes the item with the specified key and returns the value. If the key is not found, it returns the default specified (in this case, None), so it won’t raise a KeyError if the environment variable doesn’t exist.

    Then, importlib.reload(conf) is used to reload the module, re-executing the module-level code and therefore picking up the updated environment variables.

    This should give you the desired result, assuming that there are no other lingering references to the old module object.

    Login or Signup to reply.
  2. What is likely taking place there is that your src.config file is being imported elsewhere in your tests routine, before this test is run.

    Python will only read and execute a module once in a single process. Further import statements targeting the same module will just alias the name after the import statement to the already imported module.

    But the lines in the config module that do all the environment cheking and setting had already run, in a moment when the environment variables were present.

    One way to get this working without changing things would be to use importlib.reload on the config.src module – this will force the re-execution of the module, and the conf.* attributes will change.

    The main issue with this test is that you are not restoring the previous values of conf after running it – this will likely break further tests (and even, in the worst case, make tests run with values pointing to production resources used as defaults) .

    Anyway, you can overcome that by using a try…finally clause, or setup and tear-down methods as proper. (this is easier if you are using pytest instead of unittest)

    Anyway, for a self-contained test function;

    import importlib, os
    import src.config
    
    def test_without_env(self):
         original_env = os.environ.copy()
         # make changes to os.environ. `del os.environ[<KEY>]` will do.
         
         try:
             conf = importlib.reload(src.config)  # forces module to be re-executed.
             # Do your assertions with conf.* here:
             ...
         finally:
             # restore your env:
             os.environ.update(original_env)
             # rerun the config so `conf` is corrected for the remaining tests:
             importlib.reload(src.config)
    

    And last, but not least: once a Python program is started, the OS enviroment variables are copied to the os.environ dictionary, which mostly behaves as a regular Python dictionary (although it may be the target of specialized functions). So, we are making dicionary changing things, and forcing code to run, and it is ok, as long as the changes are needed in the same process.

    If you were running other processes in the container, nothing of this kind would change the env variables for them – the only way to affect the environment variables of a process is when you are the parent process that spawns a sub-process: then you can set the child environment.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search