In Python we can initialise variables at the top-level in the following way:
1is_production = True
I recently ran into a quirk of this behaviour when trying to mock the return value of os.getenv
which had a default value. I was trying to parse a boolean value from an environment variable whose default value was evaluated as follows:
1is_production = os.getenv("IS_PRODUCTION", "true").upper() == "TRUE"
I mocked os.getenv
using monkeypatch as recommended by pytest only to discover that using importing at the top-level was insufficient.
1import pytest
2
3from const import is_production
4
5def test_is_production_is_false(monkeypatch):
6 monkeypatch.setenv("IS_PRODUCTION", "false")
7 assert is_production is False
8 # True because the import and therefore the os.getenv(...) default value
9 # was used before monkeypatch set the environment variable
To the horror of many linters I tried to mitigate this by moving the import inside the test function itself.
1import pytest
2
3def test_is_production_is_false(monkeypatch):
4 monkeypatch.setenv("IS_PRODUCTION", "false")
5 from const import is_production
6 # Now monkeypatch patches the environment variable before the call to
7 # os.getenv(...) is executed
8 assert is_production is False
This is ugly if you wish to test different values so I turned it into a parameterised test function
1import pytest
2
3# Note the quotation marks
4@pytest.mark.parametrize("env_var, expected", [
5 ("True", True),
6 ("TRUE", True),
7 ("tRuE", True),
8 ("false", False),
9 ("FALSE", False),
10])
11def test_is_production(env_var, expected, monkeypatch):
12 monkeypatch.setenv("IS_PRODUCTION", env_var)
13 from const import is_production
14 assert is_production is expected
You’ll notice however, that the order of the parameters now matter and can cause test failures when an expected False
comes after a True
because the import line is evaluated once for the fixture. To solve this we simply need to delete the imported module from the system modules.
1import sys
2import pytest
3
4@pytest.mark.parametrize("env_var, expected", [
5 ("True", True),
6 ("TRUE", True),
7 ("tRuE", True),
8 ("false", False),
9 ("FALSE", False),
10])
11def test_is_production(env_var, expected, monkeypatch):
12 monkeypatch.setenv("IS_PRODUCTION", env_var)
13 from const import is_production
14 assert is_production is expected
15 del sys.modules["const"]
This should ensure your tests all pass no matter what order they run in.