Search code examples
pythonfastapi

FastAPI testing of lifespan method calls actual method instead of mock


I've got an app with FastAPI, and I'm trying to test that the lifespan method on the app is working correctly. This is my code:

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Starting up IAM APP
    print("entering lifespan")
    if mm_settings.MIGRATE_CONFIGURATION:
        await migrate_configuration_service_providers()
    yield
    print("exiting lifespan")


app = FastAPI(root_path=mm_settings.ROOT_PATH, lifespan=lifespan)

and the test

async def test_lifespan_methods_called(self):
    with patch.object(
        app.router, "lifespan_context", wraps=app.router.lifespan_context
    ) as mock_lifespan:
        async with LifespanManager(app) as manager:
            client = TestClient(manager.app)
            client.get("/")  # Trigger the lifespan

        assert mock_lifespan.called  # Ensure that the lifespan was used

The LifespanManager I am using is asgi-lifespan. The test passes. However, instead of mocking the lifespan method, it actually executes it. This I can tell both from the printed message in the lifespan method and in migrate_configuration_service_providers (if the function is successfully mocked, they shouldn't show), and the fact that there are changes in the db according to what is done in migrate_configuration_service_providers. I have tried mocking migrate_configuration_service_providers as well, but that doesn't work either. The actual function is called instead of the mock.

Edit: Someone suggested I patch the migrate_configuration_service_providers() function instead of the entire lifespan, that doesn't work either.

@patch("iam.util.migrations.migrate_configuration_service_providers")
    @pytest.mark.asyncio
    async def test_should_call_migrate_configuration_service_providers(
        self,
        migrate_configuration_service_providers_mock: Mock,
        test_client: TestClient,
    ):
        async with LifespanManager(app):
            client = TestClient(app)
            client.get("/")
        migrate_configuration_service_providers_mock.assert_called()

This gives me an AssertionError that migrate_configuration_service_providers_mock was not called, but the statements inside lifespan and migrate_configuration_service_providers() get printed and the changes in the db done by migrate_configuration_service_providers() are performed. In short: the mock is not called, the actual function is.


Solution

  • You need to patch migrate_configuration_service_providers where your lifespan function looks for it. Since you directly call the function in lifespan, that means you need to patch the function in the module that lifespan is defined in. So unless you define lifespan in iam.util.migrations, That patch won't work.

    If you define lifespan in foo.bar then the following patch would be appropriate.

    async def test_lifespan_methods_called(self):
        with patch('foo.bar.migrate_configuration_service_providers') as mock:
            async with LifespanManager(app) as manager:
                client = TestClient(manager.app)
                client.get("/")  # Trigger the lifespan
    
            assert mm_settings.MIGRATE_CONFIGURATION, (
                'Patch this setting to be True if this assert fails. '
                'Your lifespan function requires it be True for '
                'migrate_configuration_service_providers to be called.'
            )
            assert mock.called  # Ensure that the lifespan was used