You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

unittest.AsyncMock设置side_effect未抛异常却返回协程的问题咨询

关于AsyncMock与异步函数patch的问题

问题背景与可复现代码

基于Python 3.11,仅依赖pytest的最小可复现示例:

# 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:
        # 此写法失败
        mocked_get_performance.return_value.get_score.side_effect = CustomException(
            "Mocked to always fail"
        )

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

遇到的问题

尝试patch异步函数get_performance,使其返回对象的get_score方法始终抛出异常,但实际未抛出异常,而是得到coroutine object AsyncMockMixin._execute_mock_call。根据官方文档,调用AsyncMock会返回异步函数,需await才会触发side_effect或return_value,且默认AsyncMock.return_value返回的是AsyncMock。

显式将get_score设置为Mock则可正常运行:

async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # 此写法有效
        mocked_get_performance.return_value.get_score = Mock(
            side_effect=CustomException("Mocked to always fail")
        )

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

疑问

  1. 这种显式设置为Mock的方式是否可行?
  2. 是否有更优方案?
  3. 是否存在对AsyncMock的理解误区?

解答

1. 显式设置为Mock的方式完全可行

因为get_score是同步方法,用普通的Mock来模拟完全符合它的方法类型,不会有任何问题。这种写法逻辑清晰,直接匹配了原方法的同步特性,测试行为和预期一致,完全可以使用。

2. 更优方案:明确指定返回对象的类型(替代默认AsyncMock)

因为patch异步函数时,默认返回的return_value是AsyncMock,但我们需要的是一个带有同步get_score方法的普通对象,所以可以直接创建一个Mock实例作为get_performance的返回值,再给它的get_score属性设置side_effect,这样更符合语义:

async def test_main():
    with patch("minum_reproducible_example.get_performance") as mocked_get_performance:
        # 创建普通Mock作为返回对象,而非默认的AsyncMock
        mock_performance = Mock()
        mock_performance.get_score.side_effect = CustomException("Mocked to always fail")
        mocked_get_performance.return_value = mock_performance

        with pytest.raises(CustomException):
            await main()

另外,也可以用MagicMock(Mock的子类,默认实现了更多魔法方法),效果和Mock一致,但在更复杂场景下更灵活。

3. 你对AsyncMock的理解误区

核心问题在于:AsyncMock是用来模拟异步函数/方法的,而你的get_score是同步方法。

当你patch异步函数get_performance时,mocked_get_performance本身是AsyncMock,它的return_value默认也是AsyncMock——这意味着mocked_get_performance.return_value.get_score默认是一个异步Mock方法,调用它会返回协程对象,而不是直接执行side_effect。但原代码中get_score是同步方法,直接调用不需要await,所以你试图给异步Mock设置side_effect后,调用它得到的是协程对象,自然不会抛出异常,反而会得到你看到的coroutine object。

总结:AsyncMock只应该用来模拟异步方法,同步方法用普通Mock/MagicMock即可,不要混用。


内容的提问来源于stack exchange,提问作者largehadroncollider

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:47:25