Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to run pytest conftest before everything is imported

This is a simplified version of my problem.

I have a python application with the structure as below:

my_project
    my_app
        __init__.py
        settings.py
    tests
        __init__.py
        conftest.py
        my_test.py
     venv
# settings.py

from dotenv import load_dotenv
load_dotenv() 
MY_VALUE = os.environ["MY_KEY"]

I dont want to add a .env file in here for some reasons. I also dont want to use get method on os.environ

I want to run this test

# my_test.py

from my_app import settings # I need this for some reasons

def test_something():
    assert True

def test_env():
    assert os.environ["MY_KEY"] == 'MY_VALUE'

However, I get a KeyError because once I import settings.py, the line MY_VALUE = os.environ["MY_KEY"] is run and because MY_KEY is not in env, I get KeyError.

What I thought is that I could have conftest as below

import os
from unittest import mock

import pytest


@pytest.fixture(autouse=True)
def set_env(config):
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

But it is still the same problem.

How can I enforce this conftest before everything else.

also note that if I comment out from my_app import settings both my tests will pass

NOTE: the reason I do not want to use os.environ.get("MY_KEY") is that I want to enforce env variable to exist before the application or the test is initialized. The reason for this is that in reality, when my application is running in a docker container, the env variables are there before everything else and I want to increase dev-prod parity. https://12factor.net/dev-prod-parity

like image 865
Amin Ba Avatar asked Dec 18 '25 16:12

Amin Ba


1 Answers

This occurs because pytest first collects all tests and then runs them.

During the collection phase, pytest imports conftest.py and my_test.py.

During the test running phase, pytest applies fixtures and runs the test functions themselves.

In your case, during the collection phase, my_test.py imports my_app/settings.py, resulting in the side effect of reading the environment variable os.environ["MY_KEY"]. Since fixtures have not been applied yet, a KeyError is raised.

There are two ways to resolve this issue:

1. Structure your code without side effects.

Instead of initializing the settings inside the module, the initialization could be moved to the get_settings function. Additionally, functools.cache can be used to ensure that the configuration is loaded only once.

# my_app/settings.py
import functools
from dotenv import load_dotenv

@functools.cache
def get_settings():
    load_dotenv() 
    MY_VALUE = os.environ["MY_KEY"]
    return {"MY_KEY": MY_VALUE}


# tests/conftest.py
import os
from unittest import mock
import pytest

@pytest.fixture(autouse=True)
def set_env():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

# tests/my_test.py
from my_app import get_setting


def test_env():
    settings = get_setting()
    assert settings["MY_KEY"] == 'MY_VALUE'

2. Lazy import of settings.py

If the first point is not applicable, settings.py could be imported inside a test. This way, it will be imported in the test run phase, after the fixture has been applied.

# my_app/settings.py
from dotenv import load_dotenv
load_dotenv() 
MY_VALUE = os.environ["MY_KEY"]


# tests/conftest.py
import os
from unittest import mock
import pytest

@pytest.fixture(autouse=True)
def set_env():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

# tests/my_test.py
def test_env():
    from my_app import settings
    
    assert settings.MY_KEY == 'MY_VALUE'

Note that Python will cache the settings after the first import. If you want to run multiple tests with different settings, you could:

# tests/conftest.py
import os
import importlib
from unittest import mock
import pytest

@pytest.fixture
def set_env_1():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE_1'}):
        yield

@pytest.fixture
def set_env_2():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE_2'}):
        yield

@pytest.fixture
def settings():
    from py_helpers import settings
    return importlib.reload(settings)


# tests/my_test.py
def test_env_my_key_1(set_env_1, settings):
    assert settings.MY_KEY == 'MY_VALUE_1'

def test_env_my_key_2(set_env_2, settings):
    assert settings.MY_KEY == 'MY_VALUE_2'
like image 110
Vadim Denisov Avatar answered Dec 21 '25 07:12

Vadim Denisov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!