admin管理员组

文章数量:1122832

Here's a minimal reproducible example. This is with python 3.11. Besides pytest, no other dependency.

# minum_reproducible_example.py
from typing import Literal
from unittest.mock import Mock, patch
import pytest


class CustomException(Exception):
    pass


class Performance:
    status: Literal["ok", "failed"]
    score: int

    def __init__(self, status, score) -> None:
        self.status = status
        self.score = score

    def get_score(self):
        if self.status == "ok":
            return self.score
        else:
            raise Exception("Failed")


async def get_performance():
    return Performance(status="done", score=100)


async def main():
    performance = await get_performance()

    return performance.get_score()


async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # This fails
        mocked_get_performance.return_value.get_score.side_effect = CustomException(
            "Mocked to always fail"
        )

        with pytest.raises(CustomException):
            assert await main() == 100

In this example I am trying to patch get_performance so that it returns an object whose get_score method will always raise.

I don't understand what the correct way to patch get_performance is because instead of raising I get coroutine object AsyncMockMixin._execute_mock_call. This all behaves according to the documentation .mock.html#unittest.mock.AsyncMock, where calling the AsyncMock results in "an async function which will have the outcome of side_effect or return_value after it has been awaited" and clearly I am not awaiting it. This is also because by default AsyncMock.return_value will return an AsyncMock.

In fact, if I explicitly set it as Mock, it works:

async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # This works
        # mocked_get_performance.return_value.get_score = Mock(
        #     side_effect=CustomException("Mocked to always fail")
        # )

        with pytest.raises(CustomException):
            assert await main() == 100

So an apt question is "what are you expecting then".

Is this "a good enough" way to do it? Are there better ways? Am I misunderstanding something?

Thanks.

Here's a minimal reproducible example. This is with python 3.11. Besides pytest, no other dependency.

# minum_reproducible_example.py
from typing import Literal
from unittest.mock import Mock, patch
import pytest


class CustomException(Exception):
    pass


class Performance:
    status: Literal["ok", "failed"]
    score: int

    def __init__(self, status, score) -> None:
        self.status = status
        self.score = score

    def get_score(self):
        if self.status == "ok":
            return self.score
        else:
            raise Exception("Failed")


async def get_performance():
    return Performance(status="done", score=100)


async def main():
    performance = await get_performance()

    return performance.get_score()


async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # This fails
        mocked_get_performance.return_value.get_score.side_effect = CustomException(
            "Mocked to always fail"
        )

        with pytest.raises(CustomException):
            assert await main() == 100

In this example I am trying to patch get_performance so that it returns an object whose get_score method will always raise.

I don't understand what the correct way to patch get_performance is because instead of raising I get coroutine object AsyncMockMixin._execute_mock_call. This all behaves according to the documentation https://docs.python.org/3/library/unittest.mock.html#unittest.mock.AsyncMock, where calling the AsyncMock results in "an async function which will have the outcome of side_effect or return_value after it has been awaited" and clearly I am not awaiting it. This is also because by default AsyncMock.return_value will return an AsyncMock.

In fact, if I explicitly set it as Mock, it works:

async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # This works
        # mocked_get_performance.return_value.get_score = Mock(
        #     side_effect=CustomException("Mocked to always fail")
        # )

        with pytest.raises(CustomException):
            assert await main() == 100

So an apt question is "what are you expecting then".

Is this "a good enough" way to do it? Are there better ways? Am I misunderstanding something?

Thanks.

Share Improve this question asked Nov 22, 2024 at 17:31 largehadroncolliderlargehadroncollider 957 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

You are supposed to patch the return value of get_performance.

with patch(
        target="minum_reproducible_example.get_performance",
        return_value=Mock(
            spec=Performance,
            **{"get_score.side_effect": CustomException("Mocked to always fail")}
        )
):

本文标签: