long2ice / fastapi-cache

fastapi-cache is a tool to cache fastapi response and function result, with backends support redis and memcached.
https://github.com/long2ice/fastapi-cache
Apache License 2.0
1.32k stars 161 forks source link

Unable to use pytest with cache #49

Open devanchohan opened 2 years ago

devanchohan commented 2 years ago

I have a few integration tests than run as part of jenkins job, but I have been unable to get pytests to run and connect to the redis cache. I have tried this but had the same error https://fastapi.tiangolo.com/advanced/testing-events/

Has anyone found a way to test their routes that have a cache?

devanchohan commented 2 years ago

Anyone faced this issue? I tried the methods outlined in here: https://github.com/tiangolo/fastapi/issues/2003 but I still hit the same issue and tests fail

sac-cas commented 2 years ago

What problems do you encounter? I finally handled the init error but unfortunately my service function used by the cached endpoint has not been called once. Is there a way to enable/disable the cache for testing or to simulate a cached/uncached request?

sergeytol commented 2 years ago

I faced the same problem. It fails with "You must call init first!"

hh-h commented 2 years ago

add to your conftest.py file

from unittest import mock

mock.patch("fastapi_cache.decorator.cache", lambda *args, **kwargs: lambda f: f).start()
Mark1002 commented 1 year ago

you also can try this code below to mock cache.

# conftest.py
from unittest import mock

def mock_cache(*args, **kwargs):
    def wrapper(func):
        @wraps(func)
        async def inner(*args, **kwargs):
            return await func(*args, **kwargs)
        return inner
    return wrapper

mock.patch("fastapi_cache.decorator.cache", mock_cache).start()    

@pytest.fixture(scope="module")
async def client():
    from app.main import app # need to load app module after mock. otherwise, it would fail
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client
tpthian commented 1 year ago

Hi @Mark1002,

Sorry for being a noop here , but I'm hitting error when trying to use the fixture client. How is client fixture supposed to be used in a test function ?

I have the conftest.py setup as above, then my code below yield error.

import pytest

from app.main import init_app

@pytest.mark.anyio
async def test_api_get_news_details(asyncclient):
    response = await asyncclient.get("/news/34986915")

    assert response.status_code == 200

and here's the error

fixture 'mock_decorator' not found
>       available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, client, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory

What should I do now?

shinyco commented 1 year ago

@tpthian Just put the following code above your imports where you are calling the @cache

mock.patch("fastapi_cache.decorator.cache", lambda *args, **kwargs: lambda f: f).start()

Mark1002 commented 1 year ago

@tpthian sorry this is my mistake. remove parameter mock_decorator in async def client. i have edit my code snippet above.

mjpieters commented 1 year ago

This is a potential area where documentation could provide an example.

CharlesPerrotMinotHCHB commented 1 year ago

you also can try this code below to mock cache.

# conftest.py
from unittest import mock

def mock_cache(*args, **kwargs):
    def wrapper(func):
        @wraps(func)
        async def inner(*args, **kwargs):
            return await func(*args, **kwargs)
        return inner
    return wrapper

mock.patch("fastapi_cache.decorator.cache", mock_cache).start()    

@pytest.fixture(scope="module")
async def client():
    from app.main import app # need to load app module after mock. otherwise, it would fail
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client

On my end, none of the suggested solutions worked for me, all failing with 'function' object has no attribute 'start'

dicolasi commented 12 months ago

I am struggling with the same identical problem

dicolasi commented 12 months ago

ok this works for me.

Create a file called conftest.py within your module and paste this:

from unittest import mock

def mock_cache():
    mock.patch("fastapi_cache.decorator.cache", lambda *args, **kwargs: lambda f: f).start()

def pytest_sessionstart(session):
    mock_cache()
Abramov0Alexandr commented 10 months ago

@tpthianПросто поместите следующий код над импортом, где вы вызываете @cache

mock.patch("fastapi_cache.decorator.cache", lambda *args, **kwargs: lambda f: f).start()

thx for the reply it really helped me and solved this problem, but none linter allows to set this code on top😥. On the other hand other solutions don't help me, so we have what we have

jonathanhle commented 7 months ago

you also can try this code below to mock cache.

# conftest.py
from unittest import mock

def mock_cache(*args, **kwargs):
    def wrapper(func):
        @wraps(func)
        async def inner(*args, **kwargs):
            return await func(*args, **kwargs)
        return inner
    return wrapper

mock.patch("fastapi_cache.decorator.cache", mock_cache).start()    

@pytest.fixture(scope="module")
async def client():
    from app.main import app # need to load app module after mock. otherwise, it would fail
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client

this worked for me. thank you @Mark1002. i was stuck on this for sooooo long - here's the top of my conftest.py if it helps anyone else in the future:

from functools import wraps
from unittest import mock

import pytest
from fastapi.testclient import TestClient

# We need to disable the @cache decorator during testing
def mock_cache(*args, **kwargs):
    def wrapper(func):
        @wraps(func)
        async def inner(*args, **kwargs):
            return await func(*args, **kwargs)

        return inner

    return wrapper

mock.patch("fastapi_cache.decorator.cache", mock_cache).start()

# FastAPI "app" instance needs to be imported after this mocking
from ..main import app

@pytest.fixture(scope="module")
def client():
    with TestClient(app) as test_client:
        yield test_client
PavelShaura commented 3 months ago

I faced the same problem. It fails with "You must call init first!"

The error occurs because FastAPICache has not been initialized before use. Here are steps you can take to solve this problem:

Initialize FastAPICache in your main application file (usually main.py). You should do this after creating an instance of FastAPI. For tests, you will need to create a dummy cache backend.

This solution worked for me: 1.In your main application file (e.g. app/main.py):

@app.on_event("startup"). async def startup(): FastAPICache.init(InMemoryBackend())

2.In the conftest.py file, added the following:

@pytest.fixture(autouse=True, scope="function") def fastapi_cache(): FastAPICache.init(InMemoryBackend())

  1. Make sure your tests import and use this fixture:

@pytest.mark.asyncio async def test_add_data(ac: AsyncClient, fastapi_cache):

Your test code is here

Artem4es commented 2 months ago

seems to be the cleanest solution!