pytest-mock中side_effect抛异常的正确写法是直接赋值异常类或实例,如side_effect=valueerror;传字符串会被当返回值而非抛出;装饰器中需在patch参数里指定,不能事后赋值;验证时应使用pytest.raises匹配异常类型和消息。

pytest-mock里side_effect抛异常的写法
直接用 side_effect 赋值一个异常类或实例,就能让 mock 在调用时抛出该异常。这不是“模拟异常”,而是让 mock 行为本身触发异常——这是最常用、也最符合测试意图的方式。
常见错误是试图在 return_value 里塞异常(它只会原样返回,不会抛),或者误以为 side_effect 只能接函数。
-
side_effect=ValueError:调用时抛ValueError()(无参实例) -
side_effect=ValueError("boom"):抛带消息的实例 -
side_effect=[1, 2, ValueError]:前两次返回值,第三次抛异常 -
side_effect=lambda: _raise(ValueError("custom")):需要动态逻辑时用闭包(_raise是个简单抛异常函数)
为什么side_effect传异常类而不是字符串?
因为 pytest-mock(底层是 unittest.mock)会自动调用异常类构造实例。如果传字符串,mock 会把它当返回值,而不是抛出;传类则触发 raise ExcClass();传实例则直接 raise exc_instance。
典型误用:side_effect="ConnectionError" → 不抛异常,而是返回字符串 "ConnectionError";正确写法是 side_effect=ConnectionError 或 side_effect=ConnectionError("timeout")。
注意:若被测代码捕获的是具体异常类型(比如 except requests.RequestException),mock 抛的异常必须是其子类或同类型,否则 except 捕不到。
在patch装饰器里用side_effect的坑
装饰器形式下,side_effect 必须作为 patch 的参数传入,不能等 patch 完再设属性。很多人先 @patch("xxx.yyy"),然后在测试函数里写 mock_obj.side_effect = ...,这其实无效——因为装饰器已创建并注入了 mock,后续赋值不生效。
- ✅ 正确:
@patch("requests.get", side_effect=ConnectionError) - ✅ 正确:
with patch("os.listdir", side_effect=PermissionError): - ❌ 错误:
@patch("time.sleep")+mock_sleep.side_effect = TimeoutError(此时mock_sleep是新对象,原 patch 的行为没变)
如果必须运行时决定异常类型,用上下文管理器更可控,避免装饰器提前固化行为。
验证异常是否真被抛出且类型匹配
别只靠 pytest.raises 看有没有异常,还要确认类型和消息是否与预期一致,否则可能漏掉异常类型不匹配的问题。
示例:
def test_fetch_fails_with_connection_error(mocker):
mocker.patch("requests.get", side_effect=requests.ConnectionError("network down"))
with pytest.raises(requests.ConnectionError, match="network down"):
fetch_data()
注意:match 参数做正则匹配,比单纯 pytest.raises(ConnectionError) 更健壮;如果被测函数里用了 raise e from original_exc,要留意异常链是否影响断言——通常只需断最外层异常。
mock 的 side_effect 是行为模拟的核心,但它的执行时机和异常传播路径必须和真实调用一致,否则测试通过但线上仍崩。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











