Python FASTAPI Testing
Introduction
This page is to capture how to go about testing with FAST API
MonkeyPatch
So you can patch code for testing with monkey patch
monkeypatch.setattr("module.path.name", replacement)
Patch a Function
So you can
monkeypatch.setattr("module.path.get_user", lambda id: fake_user)
Which is like
jest.mock("./getUser", () => () => fakeUser)
Patch a module‑level variable
Setting a variable
monkeypatch.setattr(settings, "API_KEY", "test-key")
Which is like
jest.mock("./settings", () => ({ API_KEY: "test-key" }))
Patch an imported reference
If a module does
from server.encryptors.factory import get_encryptor
Then you can patch it like this
monkeypatch.setattr("server.module.that.uses.it.get_encryptor", fake)
Comparison to Jest
Here is a table to help compare terminology
| Concept | Jest | pytest |
|---|---|---|
| Test setup | beforeEach, beforeAll
|
Fixtures (function, module, session scope) |
| Test teardown | afterEach, afterAll
|
Fixture finalizers / yield fixtures |
| Dependency injection | Manual mocks, passing objects | Fixture injection by name |
| Auto‑applied setup | Global beforeEach in setup files
|
@pytest.fixture(autouse=True)
|
| Mocking | jest.mock, jest.spyOn
|
monkeypatch.setattr, unittest.mock.patch
|
| Test data factories | Helper functions | Fixtures returning objects |
Fixtures
These are things you create to set up a test. The autouse means you do not need to pass the fixture to the tests.
# Force argon2 for testing purposes
@pytest.fixture(autouse=True)
def force_argon2(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(settings, "authentication_encryptor", EncryptorNames.ARGON2.value)
monkeypatch.setattr(user_management.password_manager, "encryptor", get_encryptor(EncryptorNames.ARGON2))
The first sets variable authentication_encryptor in the module settings to the Enum EncryptorNames.ARGON2.value
The second sets the class property encryptor for module user_management.password_manager to the result of get_encryptor(EncryptorNames.ARGON2).
@patch
You can replace a function during a test using @patch. This does not change the function everywhere — it only replaces the name inside the module you specify.
In this example, any call to `_get_msal_app` inside `server.routers.ms_auth` will raise `ConfigurationError("bad config")` instead of running the real function.
@patch(
"server.routers.ms_auth._get_msal_app",
side_effect=ConfigurationError("bad config")
)
async def should_redirect_on_msal_config_error(
self,
_mock_get_app, # injected mock (unused)
test_client
):
response = await test_client.get("/api/ms/authorize")
The parameter `_mock_get_app` is required because @patch injects the mock into the test function. Even if you don't use it, the test must accept it.
Replacing Dependencies
FastAPI allows you to replace dependencies during testing. This is done using a special dictionary on the FastAPI app:
app.dependency_overrides[original_dependency] = replacement_dependency
FastAPI checks this dictionary at request time. If an override exists, FastAPI calls the replacement instead of the real dependency.
This lets you “fake” authentication, database access, or any other dependency.
Example:
@pytest.fixture(autouse=True)
def override_thing_dependency():
def fake_thing():
class FakeThing:
def skip_thing_function(self):
pass
return FakeThing()
# Replace the real dependency with our fake version
app.dependency_overrides[thing_injected] = fake_thing
# After the test, restore the original dependency
yield
app.dependency_overrides.pop(thing_injected, None)
This ensures every test receives the fake dependency instead of the real one.
Fixtures and Yield
If you use fixtures with yield pytest re-implements yield and adds a try, finally around the yield to ensure the takedown occurs.
import pytest
state = {"cleaned": False}
@pytest.fixture
def f():
print("SETUP")
# Implemented by pytest
# try:
yield
# finally
print("TEARDOWN")
state["cleaned"] = True
def test_fail(f):
print("TEST BODY")
raise RuntimeError("boom")