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

如何在Typer测试中为CliRunner传递或模拟Context上下文

Typer 单元测试中传递/模拟 Context 的正确方式

问题场景

我有一个从typer.Context中获取参数的Typer命令函数:

@app.command()
def my_func(
    ctx: typer.Context,
):
    settings = ctx.obj.settings

尝试用CliRunner编写单元测试时:

from typer.testing import CliRunner
runner = CliRunner()

def test_my_func():
    res = runner.invoke(app, ["my-func"])
    assert res.exit_code == 0

运行测试时抛出错误:

<Result AttributeError("'NoneType' object has no attribute 'settings'")>

我试过用@patch("my_proj.my_module.typer.Context")模拟Context,但没有效果,想知道测试时为Runner传递或模拟Context的正确方式。

解决方案

方法1:通过runner.invoke的obj参数直接传递

Typer的CliRunner.invoke原生支持obj参数,该参数会被直接赋值给ctx.obj,你只需要构造好包含settings的模拟对象即可:

from typer.testing import CliRunner
from types import SimpleNamespace

runner = CliRunner()

def test_my_func():
    # 构造模拟的上下文对象,包含settings属性
    mock_ctx_obj = SimpleNamespace(
        settings=SimpleNamespace(some_config="test_value")
    )
    # 调用invoke时传入obj参数
    res = runner.invoke(app, ["my-func"], obj=mock_ctx_obj)
    assert res.exit_code == 0

如果需要更复杂的模拟行为,也可以用自定义类:

class MockSettings:
    def __init__(self):
        self.some_config = "test_value"

def test_my_func():
    res = runner.invoke(app, ["my-func"], obj=SimpleNamespace(settings=MockSettings()))
    assert res.exit_code == 0

方法2:模拟上下文的初始化逻辑

如果你的ctx.obj是通过Typer应用的全局回调(app.callback())初始化的,可以直接模拟这个初始化过程,避免修改测试调用逻辑:

from typer.testing import CliRunner
from unittest.mock import patch

runner = CliRunner()

def test_my_func():
    # 替换实际的settings获取函数,返回模拟对象
    with patch("my_proj.my_module.load_settings") as mock_load:
        mock_load.return_value = MockSettings()
        res = runner.invoke(app, ["my-func"])
        assert res.exit_code == 0

为什么直接patch typer.Context无效?

typer.Context是Typer在命令执行时动态创建的实例,直接patch Context类无法影响到实际传入函数的具体实例。正确的思路是控制ctx.obj的内容,而非修改Context类本身。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 08:15:41