'Best way to override FastAPI dependencies for testing with a different dependency for each test

According to FastAPI official documentation the recommended way to override the dependencies for testing is to do it globally before all tests are run:

    async def override_dependency(q: Optional[str] = None):
        return {"q": q, "skip": 5, "limit": 10}


    app.dependency_overrides[common_parameters] = override_dependency


    def test_override_in_items():
        response = client.get("/items/")
        assert response.status_code == 200
        assert response.json() == {
            "message": "Hello Items!",
            "params": {"q": None, "skip": 5, "limit": 10},
        }


    def test_override_in_items_with_q():
        response = client.get("/items/?q=foo")
        assert response.status_code == 200
        assert response.json() == {
            "message": "Hello Items!",
            "params": {"q": "foo", "skip": 5, "limit": 10},
        }

But this lets you override the dependency just once for the entire test run. What if I need to have different dependencies for each test? Is it safe to override the dependency inside of the test body and reset them after the test has finished? Like this

def test_override_in_items():

    app.dependency_overrides[common_parameters] = override_dependency

    response = client.get("/items/")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello Items!",
        "params": {"q": None, "skip": 5, "limit": 10},
    }

    app.dependency_overrides[common_parameters] = {}

What are the drawbacks of doing it like the example above?



Solution 1:[1]

This is what I use myself to override dependencies in my tests. You have to use it as a context manager with a with statement (See examples below). Upon entering the context it saves existing overrides it encounters. These will be restored when exiting the context.

import typing
from fastapi import FastAPI


class DependencyOverrider:
    def __init__(
        self, app: FastAPI, overrides: typing.Mapping[typing.Callable, typing.Callable]
    ) -> None:
        self.overrides = overrides
        self._app = app
        self._old_overrides = {}

    def __enter__(self):
        for dep, new_dep in self.overrides.items():
            if dep in self._app.dependency_overrides:
                # Save existing overrides
                self._old_overrides[dep] = self._app.dependency_overrides[dep]
            self._app.dependency_overrides[dep] = new_dep
        return self

    def __exit__(self, *args: typing.Any) -> None:
        for dep in self.overrides.keys():
            if dep in self._old_overrides:
                # Restore previous overrides
                self._app.dependency_overrides[dep] = self._old_overrides.pop(dep)
            else:
                # Just delete the entry
                del self._app.dependency_overrides[dep]

You can use it like this in your tests:

from app.main import app
from fastapi.testclient import TestClient


def test_override_in_items(client: TestClient):
    with DependencyOverrider(app, overrides={common_parameters: override_dependency}):
        response = client.get("/items/")
        assert response.status_code == 200
        assert response.json() == {
            "message": "Hello Items!",
            "params": {"q": None, "skip": 5, "limit": 10},
        }

Or one could use it as a fixture in pytest:


@pytest.fixture(scope="function")
def faked_common_parameters():
    with DependencyOverrider(
        app, overrides={common_parameters: override_dependency}
    ) as overrider:
        yield overrider


def test_override_in_items(client: TestClient, faked_common_parameters):
    # Running with the overridden dependency 
    response = client.get("/items/")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello Items!",
        "params": {"q": None, "skip": 5, "limit": 10},
    }

Solution 2:[2]

You can reset your overrides by setting app.dependency_overrides to be an empty dict:

app.dependency_overrides = {}

Solution 3:[3]

According to official documentation: https://fastapi.tiangolo.com/advanced/testing-dependencies/

If you want to override a dependency only during some tests, you can set the override at the beginning of the test (inside the test function) and reset it at the end (at the end of the test function).

Based on this, we can conclude that the author of the framework does not see any danger in this.

But user Vlad is right. You may have a dependency common to several tests and a dependency used only in one test. Having dropped all dependencies in one test, you will not pass the subsequent ones that use common dependencies. This can be attributed to inconveniences.

Therefore, in my opinion, for convenience, it is better to use some kind of auxiliary mechanism proposed here

Solution 4:[4]

I used Mat's answer and created an open-source library that adds a fixture based on the code snippet. See it here.

To use the library simply do: pip install pytest-fastapi-deps, then you'll have the fastapi_dep fixture. Use it like so:

import pytest
from fastapi.testclient import TestClient

client = TestClient(app)

def test_get_override_single_dep(fastapi_dep):
    with fastapi_dep(app).override({common_parameters: lambda: {"my": "override"}}):
        response = client.get("/items/")
        assert response.status_code == 200
        assert response.json() == {"my": "override"}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Dan
Solution 3 Maksim
Solution 4 Peter K