Dependency Overrides

Swapping a dependency for a test - the piece that makes the rest of this tier testable without a database or a network.

Overview

The mechanism

A dependency override replaces one dependency with another, for the whole app, while it is running. Its main use is testing. Your real database dependency connects to a real database. During a test, you want it to return a fake or an in-memory one. An override swaps it without touching your endpoint code.

This is one of the best reasons to use dependency injection at all. Because endpoints ask for what they need instead of creating it, you can change what they receive from outside. There is no patching of modules and no tricks with imports. You set a single entry in a dictionary.

This article starts with the mechanism itself: the dictionary on the app, and what you put into it. It then explains why it matters for the way you design and test code. You will learn that the key is the function object, not its name, so you must reference the original function exactly as the endpoint did.

Next comes reach. An override applies to the whole dependency tree, so a replaced function is swapped everywhere it appears, even in sub-dependencies. It also works at the router and app levels. Overrides stay in place until you clear them, so the article closes with how to reset them after each test.

With this technique, tests become fast, isolated, and honest about what they check.

You will see short examples that you can paste into your own test files. The aim is to leave you with a pattern that works the same way on a small project and on a large one. Many teams adopt this single technique and find that their test suites become both faster and easier to read, because each test states plainly what it replaces and why. It also removes the need for patching tools that rely on import paths. Those tools tend to break when you move code, whereas an override keeps working because it points at the function itself. Each section is short and ends with a habit you can apply straight away, so you finish with working knowledge of this technique.

app.dependency_overrides[get_db] = fake_db

A dict on the app, mapping the real callable to a swap. When FastAPI resolves a dependency it checks that dict first. Uses the stand-in if one is registered.

That is the whole feature. It needs no test framework, no patching. No change to the endpoints.

Worth knowing

app.dependency_overrides[real] = fake replaces a dependency everywhere it is used.
The key is the function object, so it must be the same one the endpoints depend on — importing it twice by different paths is the usual reason an override appears not to work.
Overriding a sub-dependency changes everything built on it, without naming the intermediate ones.
It applies to router- and app-level dependencies as well as parameters.
Overrides live on the app, so clear them between tests — app.dependency_overrides.clear() — or one test quietly changes the next.
A yield dependency can be replaced by another, so a fake session still gets setup and teardown.

Dependency Overrides

Swapping a dependency for a test, and why that makes the whole tier hands-on.

Replacing one for a test

app.dependency_overrides maps the real function to a stand-in. Every endpoint using it gets the stand-in instead.

example_01.pyFastAPI
Output

Overriding authentication

The common case: tests that exercise an endpoint's logic without constructing a real token.

example_02.pyFastAPI
Output

It reaches the whole tree

Overriding a sub-dependency changes every dependency built on it, without touching them.

example_03.pyFastAPI
Output

replacing a dependency replaces it wherever it appears, including deep inside a tree.

So replacing token at the bottom changes current_user and admin_only above it, without either being mentioned. That is often what you want. Fake the one thing at the edge. The network call, the DB, the clock. And let the real logic above it run unchanged.

It's also the reason to fake as low as possible. replacing admin_only skips the permission logic fully. Replacing token lets that logic really be tested.

Router-level dependencies too

An override replaces the function wherever it is declared, including on a router.

example_04.pyFastAPI
Output

Putting it back

Overrides live on the app, so a test that does not clear up leaks into every test after it.

example_05.pyFastAPI
Output

A yield dependency can be overridden too

Including with another yield dependency, so a fake session gets the same setup and teardown.

example_06.pyFastAPI
Output

Why it matters

all in this tier pushes needs into dependencies. The DB session, the current user, the permission check, the filters. That is good design and it would be a problem if those were hard to replace. Each test would then need a real DB and a real token.

replaces are the release valve. The endpoint keeps declaring Depends(get_db). The test decides what get_db means.

Two results worth noticing. Tests get faster. Nothing real is made. And tests get *narrower*. A test for a handler's logic isn't also a test of authentication. This Means a broken token service doesn't fail two hundred unrelated tests.

The key is the function object

This is the one thing that goes wrong. The symptom is confusing: the replace appears to be skipped.

The dict is keyed by identity. The object you use as the key must be the same object the endpoints depend on. If your test imports get_db from app.db and the router imported it from .db. Those resolve to two other module objects, you've two other functions and the replace targets the wrong one.

The fix is to be steady about import paths. When an replace silently does nothing, compare the two objects before looking anywhere else.

Router and app level too

An replace replaces the function wherever it's declared. This Includes dependencies=[...] on a router or the app.

That is what makes a router-wide authentication need pleasant to test. Without it, each test of each route under /admin would need a valid key.

Clean up

replaces live on the app object. This Often outlives a single test.

A test that sets one and doesn't drop it changes each test that runs later. The failure appears one spot unrelated with no obvious cause. In pytest the standard shape is a fixture that sets the replace, yields. Clears it. The same setup/teardown discipline as a yield dependency, applied to the test.

app.dependency_overrides.clear() drops all. del app.dependency_overrides[fn] drops one.

Beyond tests

at times handy outside testing.

A local development replace can swap a real payment provider for a recording stub, or a real mailer for one that writes to a file.

A demo build can replace a live data source with fixtures.

Both are legitimate, and both deserve care: an replace registered in production code is a piece of action that doesn't appear in any endpoint's signature. If it isn't a test, make it loud. Guarded by an clear setting, logged at startup, and hopeless to enable by accident.

What this tier gives you

Dependencies let an endpoint declare what it needs. Sub-dependencies let those needs compose. yield gives them a lifetime. Router-level declarations apply them to a section. replaces let all of it be replaced at the edges.

Together that is most of what splits a FastAPI use that stays testable from one that doesn't. And none of it needs anything beyond functions and a default arg.

Mistakes people make with Dependency Overrides

Two import paths for one function. The dict is keyed by identity. from app.db import get_db and from .db import get_db can be other objects. The replace then targets a function no one depends on, and does nothing, silently.

Not clearing between tests. replaces live on the app. One test that forgets makes a later, unrelated test fail with no visible cause.

Faking too high in the tree. replacing admin_only skips the permission logic you meant to test. replace the edge - the token, the DB, the clock - and let the real logic run.

Using them in production without saying so. An replace is action that appears in no endpoint signature. If it isn't a test, gate it behind an clear setting and log it at startup.

Losing they work on router-level dependencies. They do. This Is what makes a section-wide auth need testable.

replacing instead of designing. If a test needs six replaces, the endpoint probably depends on six things it shouldn't.

The shape in pytest

The standard setup is a fixture that sets, yields and clears - the same discipline as a yield dependency:

@pytest.fixture
def client():
    app.dependency_overrides[get_db] = fake_db
    yield TestClient(app)
    app.dependency_overrides.clear()

each test using that fixture gets the fake. No test can leak it into the next one. It's four lines. It's the gap between a suite that is trustworthy and one that fails differently based on sorting.

What good test structure looks like

replaces work best with a small amount of structure around them.

One fixture per fake. A fake_db fixture, a fake_user fixture, each setting one replace and clearing it. Tests then compose the ones they need rather than sharing a single do-all client.

Fake at the edges. replace the DB, the clock, the HTTP client, the token decoder. Don't replace the permission logic, the filters, or anything you're trying to test.

Prefer real objects to mocks. A fake_db returning a dict or an in-memory list exercises more of the real code path than a mock that asserts it was called. The point of replacing is to drop the network, not the logic.

Keep one test with nothing overridden. An integration test that exercises the real tree catches the case where the fakes have quietly diverged from what they stand in for - which is the failure mode of heavy faking.

The limits

replaces replace a dependency, and that is all they do.

They can't change what an endpoint declares. An endpoint based on a thing not needed still depends on it in tests. They don't apply to code called *inside* a handler - a handler that imports and calls get_session() right is untouched by any replace. This Is the strongest hands-on arg for declaring dependencies rather than reaching for them.

And they're per app object. Tests that make their own FastAPI() per module get alone for free. Tests that share one imported app need the discipline of clearing.

Beyond the test suite

Two uses outside testing are legitimate, and both deserve care.

Local development. Swapping a payment provider for a recording stub, or a mailer for one that writes to a file, lets a developer run the whole use without credentials for anything external. It's truly handy and it's one setting away from being enabled one spot it shouldn't be.

Demos. Replacing a live data source with fixtures gives a stable demo that doesn't depend on the state of a shared setting.

The rule for both: an replace registered outside a test is action that appears in no endpoint signature and no schema. Gate it behind an clear setting, log it loudly at startup. Make the default off. A reader of the code shouldn't have to know the replaces exist to understand what an endpoint does.

What it says about the design

A final finding. If a test needs six replaces to run one endpoint, the replaces aren't the problem - the endpoint is based on six things.

replaces make dependencies replaceable. They don't make an over-connected endpoint simple. When the fixture list grows, the handy question is whether the handler is doing work that belongs in a service, or based on things it doesn't really need.

Used that way the feature is also a design signal. The number of things you've to fake to test an endpoint is a fair measure of how entangled it's.

Why this closes the tier

The five modules in this tier fit together. Replaces are what make the setup hands-on rather than merely elegant.

Dependencies let an endpoint declare what it needs. Sub-dependencies let those needs build on each other. yield gives them a lifetime. Router-level declarations apply them to a whole section.

each one of those pushes real things - a DB, a token service, a clock - further from the handler and closer to the edge of the use. Without a way to replace them, that would make the endpoints harder to test rather than easier. The whole approach would be a net loss.

replaces invert it. Because the edges are declared rather than reached for, they can be swapped. A handler that depends on four external things can be tested with none of them present.

That is the trade this tier is really about: declaring needs instead of acquiring them. all else follows from it.

Dependency Overrides: summary

app.dependency_overrides[real] = fake replaces a dependency all over it appears, including in a tree and on a router.

The key is the function object. Inconsistent import paths are the usual reason an replace silently does nothing. replaces live on the app. So Clear them between tests or one quietly changes the next.

Fake at the edges - the DB, the clock, the token - and let the real logic above run. Keep one test with nothing overridden. The fakes can't drift from what they stand in for without a thing failing.

Summary, in one line

app.dependency_overrides[real] = fake swaps a dependency all over it appears - keyed by the function object, cleared between tests. Applied as low in the tree as possible so the logic above it still runs.

Two rules

Key on the same object the endpoints use. Inconsistent imports are the reason an replace silently does nothing.

Clear between tests. A leaked replace fails a later, unrelated test with no visible cause.

Where it sits in the tier

Last of the five, and the one that justifies the other four.

Pushing needs into dependencies moves real things - DBs, tokens, clocks - to the edge of the use. That would make testing harder if the edge couldn't be replaced. replaces are what make it replaceable. So what makes the whole approach pay.

Questions people ask about Dependency Overrides

How do I replace a dependency in a test? Set app.dependency_overrides[original_function] = fake_function. FastAPI then calls the fake.

What is the key of the overrides dict? The original function object itself, not its name. It must be the same object used in Depends().

Does it work for sub-dependencies? Yes. The override applies anywhere in the tree where that dependency is used.

How do I undo it? Call app.dependency_overrides.clear() or use a pytest fixture that yields then clears.

Does the fake need the same parameters? No. The override gets its own signature, and FastAPI resolves that signature instead.

Is it only for tests? It is mostly for tests, but it also helps with local development against a stub service.

Where to go after Dependency Overrides

The runtime: what really happens when a request arrives, why an async def endpoint that makes a blocking call stalls every other request in the process. The parts of the framework that need a real event loop.

And the failure to watch for is silence. An replace that targets a other object than the endpoints use does nothing at all, reports nothing. Leaves the test passing against the real dependency.

Check yourself

0 of 4

Answer without scrolling back up.

  1. What is `app.dependency_overrides` keyed by?

  2. You override a dependency at the bottom of a tree. What happens above it?

  3. Why clear overrides between tests?

  4. Can a router-level dependency be overridden?

Cheat sheet

Dependency Overrides

Swap a real dependency for a test double with dependency_overrides, run the test, and see why the pattern makes FastAPI simple to test.

FASTAPI · vizlearn.in/fastapi/dependency_overrides.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.