Package: fastapi-overrider
fastapi-overriderEasy and safe dependency overrides for yourFastAPItests.Installationpip install fastapi-overriderMotivationFastAPI provided a nice mechanism to override dependencies, but there are a few gotchas:Overrides are not cleaned up automatically and can't be scoped easily.Lots of boilerplate code required when you just wantsometest data.Usingunittest.mock.Mockis non-trivial due to the way FastAPI relies on inspection
of signatures when calling dependencies.Likewise, mocking async dependencies is cumbersome.The goal of fastapi-override is to make dependency overriding easy, safe, reusable, composable,
and extendable.UsageGeneral usageUse it as pytest fixture to ensure every test is run with a clean set of overrides.override=create_fixture(app)deftest_get_item_from_value(client:TestClient,override:Overrider)->None:override_item=Item(item_id=0,name="Bar")override.value(lookup_item,override_item)response=client.get("/item/0").json()assertItem(**response)==override_itemAlternatively use it as a context manager:deftest_get_item_context_manager(client:TestClient,app:FastAPI)->None:withOverrider(app)asoverride:override_item=Item(item_id=0,name="Bar")override.value(lookup_item,override_item)response=client.get("/item/0").json()assertItem(**response)==override_itemIn both cases the overrides will be cleaned up after the test.The above examples also show how to override a dependency with just the desired return
value. Overrider will take care of creating a matching wrapper function and setting it
as an override.It doesn't matter if your dependency is async or not. Overrider will do the right thing.Basic overridesoverride.value()returns the override value:deftest_get_item_return_value(client:TestClient,override:Overrider)->None:item=override.value(lookup_item,Item(item_id=0,name="Bar"))response=client.get("/item/0").json()assertItem(**response)==itemoverride.function()accepts a callable:deftest_get_item_function(client:TestClient,override:Overrider)->None:item=Item(item_id=0,name="Bar")override.function(lookup_item,lambdaitem_id:item)# noqa: ARG005response=client.get("/item/0").json()assertItem(**response)==itemUse it as a drop-in replacement forapp.dependency_overrides:deftest_get_item_drop_in(client:TestClient,override:Overrider)->None:item=Item(item_id=0,name="Bar")defoverride_lookup_item(item_id:int)->Item:# noqa: ARG001returnitemoverride[lookup_item]=override_lookup_itemresponse=client.get("/item/0").json()assertItem(**response)==itemMocks and spiesOverrider can create mocks for you:deftest_get_item_mock(client:TestClient,override:Overrider)->None:item=Item(item_id=0,name="Bar")mock_lookup=override.mock(lookup_item)mock_lookup.return_value=itemresponse=client.get("/item/0")mock_lookup.assert_called_once_with(item_id=0)assertItem(**response.json())==itemSpy on a dependency. The original dependency will still be called, but you can call assertions
and inspect it like aunittest.mock.Mock:deftest_get_item_spy(client:TestClient,override:Overrider)->None:spy=override.spy(lookup_item)client.get("/item/0")spy.assert_called_with(item_id=0)Auto-generated overridesOverrider can auto-generate mock objects usingUnifactory.To enable this extra feature, usepip install fastapi-overrider[unifactory].Overrider will automatically use a matching factory fromPolyfactory's inventoryfor the given dependency.Generate a single override value. You can provide optional keyword arguments to any of the
auto-generator methods in order to pin an attribute to a specific value, likenamein
this example:deftest_get_some_item(client:TestClient,override:Overrider)->None:item=override.some(lookup_item,name="Foo")response=client.get(f"/item/{item.item_id}")assertitem.name=="Foo"assertitem==Item(**response.json())You can also let Overrider generate multiple override values:deftest_get_five_items(client:TestClient,override:Overrider)->None:items=override.batch(lookup_item,5)foriteminitems:response=client.get(f"/item/{item.item_id}")assertitem==Item(**response.json())Attempt to cover the full range of forms that a model can take:deftest_cover_get_items(client:TestClient,override:Overrider)->None:items=override.cover(lookup_item)foriteminitems:response=client.get(f"/item/{item.item_id}")assertitem==Item(**response.json())ShortcutsYou can call Overrider directly and it will guess what you want to do:If you pass in a callable, it will act likeoverride.function():deftest_get_item_call_callable(client:TestClient,override:Overrider)->None:item=Item(item_id=0,name="Bar")override(lookup_item,lambdaitem_id:item)# noqa: ARG005response=client.get("/item/0").json()assertItem(**response)==itemIf you pass in a non-callable, it will act likeoverride.value():deftest_get_item_call_value(client:TestClient,override:Overrider)->None:item=override(lookup_item,Item(item_id=0,name="Bar"))response=client.get("/item/0").json()assertItem(**response)==itemIf you don't pass in anything, it will create a mock:deftest_get_item_call_mock(client:TestClient,override:Overrider)->None:item=Item(item_id=0,name="Bar")mock_lookup=override(lookup_item)mock_lookup.return_value=itemresponse=client.get("/item/0")mock_lookup.assert_called_once_with(item_id=0)assertItem(**response.json())==itemAdvanced patternsReuse common overrides. They are composable, you can have multiple:@pytest.fixture()defas_dave(app:FastAPI)->Iterator[Overrider]:withOverrider(app)asoverride:override(get_user,User(name="Dave",authenticated=True))yieldoverride@pytest.fixture()defin_the_morning(app:FastAPI)->Iterator[Overrider]:withOverrider(app)asoverride:override(get_time_of_day,"morning")yieldoverridedeftest_get_greeting(client:TestClient,as_dave:Overrider,in_the_morning:Overrider)->None:response=client.get("/")assertresponse.text=='"Good morning, Dave."'Extend it with your own convenience methods:classMyOverrider(Overrider):defuser(self,*,name:str,authenticated:bool=False)->None:self(get_user,User(name=name,authenticated=authenticated))@pytest.fixture()defoverride(app:FastAPI):withMyOverrider(app)asoverride:yieldoverridedeftest_open_pod_bay_doors(client:TestClient,my_override:MyOverrider)->None:my_override.user(name="Dave",authenticated=False)response=client.get("/open/pod_bay_doors")assertresponse.text=="\"I'm afraid I can't let you do that, Dave.\""