[Python] A simple guide: how to mock dependencies for unit testing in FastAPI?
Tags: webdev, python, fastapi, testing
Published: 2023-05-01 07:01:50

# TL; DR
- Use `dependency_overrides` dictionary to override the dependencies set up. The fields are the arguments of `Depends`, and the corresponding values are callables that creates the same type of the dependency objects(the first argument of `Annotated`).
- Use `unittest.mock.AsyncMock` when mocking class dependencies, for your convenience.
- either mocking function or class dependencies, there should be no parameters in the mocking callables. Otherwise you will get `RequestValidationError`s.


# Intro
When it comes to unit testing when using the [FastAPI] (https://fastapi.tiangolo.com/) framework, you may have to mock the dependencies you have set up. Beyond the [tutorial pages](https://fastapi.tiangolo.com/tutorial/), you will find the page that explains how to override [dependencies](https://fastapi.tiangolo.com/advanced/testing-dependencies/). 

However, the official documentation doesn't include the case of class dependencies, and there is a small pitfall you need to avoid. In this post, I want to share my experience of how I approached these issues.


## 1. Initial setup
For clarity, I would like to set up the most simple example that I can think of. Say we have a single endpoint “`/`” for a GET request:

```py
# example.py
from fastapi import FastAPI

from .dependencies import ExampleFunctionDependency, ExampleClassDependency

app = FastAPI()


@app.get("/")
def example_router(
    *,
    example_function_dependency: ExampleFunctionDependency,
    example_class_dependency: ExampleClassDependency
):
    return "example dependency!"

```


And in this router, we will use two dependencies of the following code:

```py
# dependencies.py
from typing import Annotated

from fastapi import Depends


def example_function() -> int:
    return 1


class ExampleClass:
    ...


ExampleFunctionDependency = Annotated[int, Depends(example_function)]
ExampleClassDependency = Annotated[ExampleClass, Depends()]

```
(*note that we use `Annotated` here, which has been adopted by FastAPI [since its version 0.95.0](https://github.com/tiangolo/fastapi/releases/tag/0.95.0)*)


## 2. Mocking dependencies
Let's dive into mocking the dependencies we prepared above. We will begin with the function dependencies, an example of which is in the official documentation.


### Function dependencies
Now according to the [official documentation](https://fastapi.tiangolo.com/advanced/testing-dependencies/), we will write a unit testing for the router with mocked dependencies.

Remember, when you specify the list of overriding dependencies, **the keys are actual functions or classes inside `Depends` function(not a simple string value!)**, and **the values are *callables* that generate the objects mimicking the dependency objects**.

So when you mock a function dependency like the following, 

```py
def example_function(query: str = Query()) -> int:
  # some code that returns `int`

ExampleDependency = Annotated[int, Depends(example_function)]
```

then the `dependency_overrides` dictionary should be like this:

```py
def mock_example_function(query: str = Query()):
  return 42

app.dependency_overrides.update({
  example_function: mock_example_function
})

```

Hence, our unittest code should be like the following:

```py
# test_example.py
from unittest import mock

import pytest
from fastapi.testclient import TestClient

from .example import app
from .dependencies import example_function, ExampleClass


@pytest.fixture
def client() -> TestClient:
    def mock_example_function() -> int:
        return 42

    app.dependency_overrides.update(
        {example_function: mock_example_function}
    )

    return TestClient(app=app)


def test_dependencies(client: TestClient):
    response = client.get("/")
    assert response.is_success
```


### Class dependencies
But what about class dependencies? Here is a pitfall: since we use a class instance as a class dependency object, we need to provide a *callable that generates either an instance of that class or a mocking object of it*. 

So either of the following cases would be fine in our case. However, if our class has many methods to be called inside the router, then it’s better to use mockers such as `unittest.mock.AsyncMock` for simplicity(well, actually that's what mock is for). Note that **we don’t provide an instance itself**.

```py
# a function that provides either mocking object or an instance of an object
def mock_example_class() -> ExampleClass:
        return mock.AsyncMock()

app.dependency_overrides.update({ 
  ExampleClass: mock_example_class
})

# directly passes our custom mocking class
class CustomMockingClass:
  …

app.dependency_overrides.update({ 
  ExampleClass: CustomMockingClass
})
```

If we choose the first option, then our `client` fixture will be like:

```py
@pytest.fixture
def client() -> TestClient:
    def mock_example_function() -> int:
        return 42
    
    def mock_example_class() -> mock.AsyncMock:
        return mock.AsyncMock()

    app.dependency_overrides.update(
        {example_function: mock_example_function, ExampleClass: mock_example_class}
    )

    return TestClient(app=app)
```

Now Let’s run `pytest` to see the following result!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qukkk8gc903forrb6iym.png)


## Caveat: Don't have any parameters in your mocking callables

This is because, if you accidentally use arguments in your mocking callable, they're recognized as query parameters in FastAPI

This issue happened to me when I tried to simplify the mocking part with lambda expressions as follows:

```py
@pytest.fixture
def client() -> TestClient:
    app.dependency_overrides.update(
        {example_function: lambda x: 42, ExampleClass: lambda x: mock.AsyncMock()}
    )

    return TestClient(app=app)
```

Then when you run `pytest`, you will get this following error:

```
    def test_dependencies(client: TestClient):
        response = client.get(url="/")
>       assert response.is_success
E       assert False
E        +  where False = <Response [422 Unprocessable Entity]>.is_success
```

Since we get `422` status, we can suspect that the error is possibly from `RequestValidationError`. Since it's beyond the scope of this post, we won't dig into how to check the error, but the reason is because our argument `x` that we accidentally put in to the lambdas are recognized as query parameters(to see the details, check the [source code](https://github.com/tiangolo/fastapi/blob/8ac8d70d52bb0dd9eb55ba4e22d3e383943da05c/fastapi/dependencies/utils.py#L452)).

Now that we know the exact cause of our issue, we can simplify the code with lambda expressions:

```py
@pytest.fixture
def client() -> TestClient:
    app.dependency_overrides.update(
        {example_function: lambda : 42, ExampleClass: lambda : mock.AsyncMock()}
    )

    return TestClient(app=app)
```

And if you run again `pytest`, the test should pass.


# Conclusion
Mocking dependencies in FastAPI is not that simple as it seems. By reducing developer's workloads, FastAPI encapsulates many of the logics behind in return, and it is pretty easy to get lost once you want to implement your own logic. Hope this post helps you with testing FastAPI applications.