Search code examples
pythonpytestfastapipydantic

How to override "env_file" during tests?


I'm reading env variables from .prod.env file in my config.py:

from pydantic import BaseSettings


class Settings(BaseSettings):
    A: int

    class Config:
        env_file = ".prod.env"
        env_file_encoding = "utf-8"

settings = Settings()

in my main.py I'm creating the app like so:

from fastapi import FastAPI
from app.config import settings

app = FastAPI()
print(settings.A)

I am able to override settings variables like this in my conftest.py:

import pytest
from fastapi.testclient import TestClient

from app.main import app
from app.config import settings

settings.A = 42

@pytest.fixture(scope="module")
def test_clinet():
    with TestClient(app) as client:
        yield client

This works fine, whenever I use settings.A I get 42.

But is it possible to override the whole env_file from .prod.env to another env file .test.env?

Also I probably want to call settings.A = 42 in conftest.py before I import app, right?


Solution

  • You can override the env file you use by creating a Settings instance with the _env_file keyword argument.

    From documentation:

    Passing a file path via the _env_file keyword argument on instantiation (method 2) will override the value (if any) set on the Config class. If the above snippets were used in conjunction, prod.env would be loaded while .env would be ignored.

    For example, this should work for your test -

    import pytest
    from fastapi.testclient import TestClient
    
    import app.config as conf
    from app.config import Settings
    
    # replace the settings object that you created in the module
    conf.settings = Settings(_env_file='.test.env')
    
    from app.main import app
    
    # just to show you that you changed the module-level
    # settings
    from app.config import settings
    
    @pytest.fixture(scope="module")
    def test_client():
        with TestClient(app) as client:
            yield client
    
    def test_settings():
        print(conf.settings)
        print(settings)
    

    And you could create a .test.env, set A=10000000, and run with

    pytest -rP conftest.py
    
    # stuff
    ----- Captured stdout call -----
    A=10000000
    A=10000000
    

    This looks a little messy (though this is probably only used for test purposes), so I'd recommend not creating a settings object that is importable by everything in your code, and instead making it something you create in, say, your __main__ that actually creates and runs the app, but that's a design choice for you to make.