如何让Pytest打补丁的函数仅首次调用触发异常副作用?
解决Pytest测试模板渲染异常分支的问题
你的核心需求是:测试main函数在第一次调用render_template抛异常时,会自动 fallback 到default.html的逻辑。之前的尝试踩了几个坑,下面给你两种简洁的解决办法:
问题分析
- 直接把
side_effect设为异常实例:每次调用都会抛出异常,导致第二次调用render_template('default.html')也失败。 - 用
[Exception, None]作为side_effect:不符合业务逻辑,render_template实际不会返回None。 - 自定义
side_effect时直接调用render_template:因为已经被patch,会触发无限递归调用,导致异常无法终止。
方案一:利用side_effect列表+原函数
先保存render_template的原函数,然后给side_effect设置一个列表:第一次抛异常,第二次调用原函数。
def test_main_exception_fallback(mocker): # 获取未被patch的原函数 from path.to import render_template as original_render # 打补丁:第一次调用抛异常,第二次调用原函数 mock_render = mocker.patch( "path.to.render_template", side_effect=[Exception("mock template error"), original_render] ) # 执行测试函数 result = main("some_path") # 验证调用逻辑:先调用some_path,再调用default.html mock_render.assert_has_calls([ mocker.call("some_path"), mocker.call("default.html") ]) # 验证结果是原函数渲染default.html的返回值 assert result == original_render("default.html")
方案二:自定义side_effect函数(带调用计数)
用计数器记录调用次数,第一次抛异常,后续调用直接走原函数逻辑,避免递归:
def test_main_exception_fallback(mocker): from path.to import render_template as original_render call_count = 0 def mock_render_side_effect(*args, **kwargs): nonlocal call_count call_count += 1 if call_count == 1: # 第一次调用抛异常 raise Exception("mock template error") # 后续调用走原函数 return original_render(*args, **kwargs) mock_render = mocker.patch( "path.to.render_template", side_effect=mock_render_side_effect ) result = main("some_path") # 验证调用顺序和参数 mock_render.assert_has_calls([ mocker.call("some_path"), mocker.call("default.html") ]) assert result == original_render("default.html")
关键注意点
- 必须调用未被patch的原函数:不能在自定义
side_effect里直接调用render_template,否则会触发递归调用。 - 用
assert_has_calls验证调用顺序:确保main确实先尝试了指定模板,再fallback到默认模板。
内容的提问来源于stack exchange,提问作者matetam
相关产品推荐
相关产品推荐

