[Python] How to correctly patch an async function for unit tests in pytest

Discussão em 'Python' iniciado por Stack, Junho 4, 2025.

  1. Stack

    Stack Membro Participativo

    I am coding a FastAPI app with async functions. My issue is, I can't properly mock async function with the mocker.patch from pytest-mock nor with AsyncMock from unittest.
    The issue is, I can't test this function in the file test/unit/services/auth_test.py:

    from typing import Optional
    from src.Exceptions.jwt_token_exceptions import (
    CREDENTIALS_EXCEPTION,
    )
    from src.models import User
    from src.security.password import verify_password
    from src.services.crud_user import get_user_by_login_with_validity_check
    from src.utils.db import SessionDep


    async def authenticate_user(
    session: SessionDep,
    username: str,
    password: str,
    ) -> Optional[User]:
    user = await get_user_by_login_with_validity_check(session, username)
    if not user:
    raise CREDENTIALS_EXCEPTION
    is_correct_password = await verify_password(password, user.hashed_password)
    if is_correct_password is not True:
    raise CREDENTIALS_EXCEPTION
    user.set_last_login_date()
    await session.commit()
    return user


    So I need to mock these functions (session is already mocked):


    • get_user_by_login_with_validity_check


    • verify_password

    Test attempt:

    from typing import Optional

    import pytest
    from unittest.mock import AsyncMock
    from src.models import User
    from src.services.auth import authenticate_user

    PLAIN_PASSWORD = "password"
    HASHED_PASSWORD = "hash"
    LOGIN = "User"
    EMAIL = "user@gmail.com"


    def get_user_mock(mocker, return_value: Optional[User]):
    return mocker.patch(
    "src.services.crud_user.get_user_by_login_with_validity_check",
    new=AsyncMock(return_value=return_value),
    )


    def verify_password_mock(mocker, return_value: bool):
    return mocker.patch(
    "src.security.password.verify_password",
    new=AsyncMock(return_value=return_value),
    )


    def session_mock(mocker):
    mock: AsyncMock = mocker.AsyncMock()
    mock.commit.side_effect = lambda: None
    return mock


    def get_user():
    return User(login=LOGIN, email=EMAIL, hashed_password=HASHED_PASSWORD)


    @pytest.mark.asyncio
    async def test_authenticate_user_success(mocker):
    user = get_user()
    mock_session = session_mock(mocker)
    mock_get_user = get_user_mock(mocker, user)
    mock_verify_password = verify_password_mock(mocker,True)

    results = await authenticate_user(mock_session, LOGIN, PLAIN_PASSWORD)


    Error logs:

    test\unit\service\auth_test.py:37 (test_authenticate_user_success)
    mocker = <pytest_mock.plugin.MockerFixture object at 0x0000028F16D0D940>

    @pytest.mark.asyncio
    async def test_authenticate_user_success(mocker):
    user = get_user()
    mock_session = session_mock(mocker)
    mock_get_user = get_user_mock(mocker, user)
    mock_verify_password = verify_password_mock(mocker,True)

    > results = await authenticate_user(mock_session, LOGIN, PLAIN_PASSWORD)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    test\unit\service\auth_test.py:45:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    src\services\auth.py:19: in authenticate_user
    user = await get_user_by_login_with_validity_check(session, username)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    session = <AsyncMock id='2813548575152'>, login = 'User'

    async def get_user_by_login_with_validity_check(
    session: SessionDep, login: str
    ) -> Optional[User]:
    user: User = await get_user_by_login(session, login)
    if user is None:
    raise USER_DOESNT_EXISTS
    > if user.deleted:
    ^^^^^^^^^^^^
    E AttributeError: 'coroutine' object has no attribute 'deleted'

    src\services\crud_user.py:57: AttributeError
    sys:1: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited


    It looks like my patch doesn't exist, because it enters the function instead of using the mock

    Continue reading...

Compartilhe esta Página