如何在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
相关产品推荐
相关产品推荐

