In Python we can initialise variables at the top-level in the following way:
is_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:
is_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.
import pytest
from const import is_production
def test_is_production_is_false(monkeypatch):
monkeypatch.setenv("IS_PRODUCTION", "false")
assert is_production is False
# True because the import and therefore the os.getenv(...) default value
# 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.
import pytest
def test_is_production_is_false(monkeypatch):
monkeypatch.setenv("IS_PRODUCTION", "false")
from const import is_production
# Now monkeypatch patches the environment variable before the call to
# os.getenv(...) is executed
assert is_production is False
This is ugly if you wish to test different values so I turned it into a parameterised test function
import pytest
# Note the quotation marks
@pytest.mark.parametrize("env_var, expected", [
("True", True),
("TRUE", True),
("tRuE", True),
("false", False),
("FALSE", False),
])
def test_is_production(env_var, expected, monkeypatch):
monkeypatch.setenv("IS_PRODUCTION", env_var)
from const import is_production
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.
import sys
import pytest
@pytest.mark.parametrize("env_var, expected", [
("True", True),
("TRUE", True),
("tRuE", True),
("false", False),
("FALSE", False),
])
def test_is_production(env_var, expected, monkeypatch):
monkeypatch.setenv("IS_PRODUCTION", env_var)
from const import is_production
assert is_production is expected
del sys.modules["const"]
This should ensure your tests all pass no matter what order they run in.