pythonasynchronousfastapipytest-asyncio

assert "object MagicMock can't be used in 'await' expression" while testing fastapi endpoint with update_one


I'm creating test cases for a FastAPI endpoint that uses motor and a keep getting assert "object MagicMock can't be used in 'await' expression.

Endpoint:

@router.put("/shield")
async def action(a: bool, mongo_client: AsyncIOMotorClient = Depends(connection)):
    try:
        col = mongo_client["db"]["col"]

        result = await col.update_one(
            {"foo": "bar"}, {"$set": {"x": a}}
        )

        if result.matched_count == 0:
            return JSONResponse(status_code=404, content="Str not found")
        elif result.modified_count == 0:
            return JSONResponse(status_code=204, content="Failed to update")
        
        return JSONResponse(status_code=200, content="update successfull")
    except Exception as e:
        return JSONResponse(status_code=500, content=str(e))

And my test case is like this:

@pytest.fixture(scope="session")
async def mock_motor_client():
    mock_client = AsyncMock()
    yield mock_client


@pytest.mark.asyncio
async def test_shield_disable_success(mock_motor_client):
    app = FastAPI()
    app.include_router(router)
    app.dependency_overrides[connection] = lambda: mock_motor_client
    client = TestClient(app)


    response = client.put("/v2/shield?a=false")

    mock_motor_client.update_one = AsyncMock(return_value=AsyncMock(matched_count=1, modified_count=1))
    assert response.status_code == 200


Solution

  • Here the minimal code to make your example works

    
    
    from unittest.mock import AsyncMock
    
    import pytest
    from fastapi import APIRouter, Depends, FastAPI
    from motor.motor_asyncio import AsyncIOMotorClient
    from starlette.responses import JSONResponse
    from starlette.testclient import TestClient
    
    router = APIRouter(prefix="/v2")
    
    async def connection() -> AsyncIOMotorClient:
        return AsyncIOMotorClient()
    
    @router.put("/shield")
    async def action(a: bool,
                     mongo_client: AsyncIOMotorClient = Depends(connection)):
        try:
            col = mongo_client["db"]["col"]
    
            result = await col.update_one(
                {"foo": "bar"}, {"$set": {"x": a}}
            )
    
            if result.matched_count == 0:
                return JSONResponse(status_code=404, content="Str not found")
            elif result.modified_count == 0:
                return JSONResponse(status_code=204, content="Failed to update")
    
            return JSONResponse(status_code=200, content="update successfull")
        except Exception as e:
            return JSONResponse(status_code=500, content=str(e))
    
    
    @pytest.fixture(scope="session")
    def mock_motor_client():
        mock_client = AsyncMock()
        mock_client.update_one = AsyncMock(return_value=AsyncMock(matched_count=1, modified_count=1))
        return {"db": {"col": mock_client}}
    
    
    @pytest.mark.asyncioasync def test_shield_disable_success(mock_motor_client):
        app = FastAPI()
        app.include_router(router)
        app.dependency_overrides[connection] = lambda: mock_motor_client
        client = TestClient(app)
    
    
        response = client.put("/v2/shield?a=false")
    
        assert response.status_code == 200
    
    

    The main changes are on the fixture of the mock_motor_client where, you have to return a dict (not a function) because it's the waited structure in your router function, you have this line: mongo_client["db"]["col"] (this could be done in the connection function). And, the second mock giving the value is also done in this fixture not in the test (it's cleaner IHMO).