使用pytest.mark.parametrize时Mock继承类方法的单元测试问题
如何结合pytest参数化与Mock避免API调用并验证URL
问题场景
我是单元测试新手,开发了一套从API获取数据、反序列化为JSON并写入数据库的类,目前这些类均可正常运行,正在编写对应的单元测试。现有类定义如下:
import requests class GetResponse(): def getDeserialize(self, url: str): ApiResponse = requests.get(f'{url}') toJson = ApiResponse.json() return toJson class MercadoBitcoin(GetResponse): def __init__(self) -> None: self.beginningOfUrl = 'https://www.mercadobitcoin.net/api' # This method is to get generally from the API def standardGet(self, coin: str, method: str): self.URL = f'{self.beginningOfUrl}/{coin}/{method}/' urlGet = super().getDeserialize(self.URL) return urlGet # This method is to get specifically from the API Day Summary def daySummary(self, year: int, month: int, day: int, coin: str): method = 'day-summary' self.URL = f'{self.beginningOfUrl}/{coin}/{method}/{year}/{month}/{day}' urlGet = super().getDeserialize(self.URL) return urlGet
我编写了测试用例仅需验证生成的URL与预期一致,无需实际调用API,但尝试的两种Mock方式均无效,请求指导正确的实现方式。
错误原因分析
- 第一种Mock
requests.get的问题:装饰器顺序导致参数传入顺序错误,patch的 mock 对象应该作为函数的第一个参数传入,而非最后一个。 - 第二种Mock
super.getDeserialize的问题:super不是可被 patch 的目标,需要明确指定要 mock 的父类方法路径。
解决方案
方案一:Mock requests.get,避免实际API调用
调整装饰器顺序(@pytest.mark.parametrize 放在 @patch 上方),并修正函数参数顺序,确保 mock 对象先传入:
from unittest.mock import patch import pytest from your_module import MercadoBitcoin # 替换为你的类所在模块名 class TestMercadoBitcoin(): @pytest.mark.parametrize( "coin, method, expected", [ ("BTC", "ticker", "https://www.mercadobitcoin.net/api/BTC/ticker/"), ("ETH", "ticker", "https://www.mercadobitcoin.net/api/ETH/ticker/") ] ) @patch('requests.get') def test_standardGet(self, mock_get, coin, method, expected): # 配置mock返回值,避免调用json()时报错 mock_response = mock_get.return_value mock_response.json.return_value = {} actual = MercadoBitcoin() actual.standardGet(coin=coin, method=method) assert actual.URL == expected # 可选:验证requests.get是否被正确调用了一次,传入的URL是否正确 mock_get.assert_called_once_with(expected)
说明:
- 装饰器顺序:
@pytest.mark.parametrize在上,@patch在下,确保参数化逻辑先处理,mock 对象作为第一个参数传入函数。 - 必须配置 mock 返回值:因为
getDeserialize会调用response.json(),所以需要给mock_get.return_value.json设置返回值,否则会抛出 AttributeError。 - 可选验证:通过
mock_get.assert_called_once_with(expected)可以进一步确认API调用的URL是否符合预期。
方案二:Mock父类GetResponse的getDeserialize方法
如果只想阻止父类的方法执行,而不是mock requests,可以直接mock GetResponse.getDeserialize:
from unittest.mock import patch import pytest from your_module import MercadoBitcoin, GetResponse # 替换为你的类所在模块名 class TestMercadoBitcoin(): @pytest.mark.parametrize( "coin, method, expected", [ ("BTC", "ticker", "https://www.mercadobitcoin.net/api/BTC/ticker/"), ("ETH", "ticker", "https://www.mercadobitcoin.net/api/ETH/ticker/") ] ) @patch.object(GetResponse, 'getDeserialize') def test_standardGet(self, mock_get_deserialize, coin, method, expected): # 配置mock返回值 mock_get_deserialize.return_value = {} actual = MercadoBitcoin() actual.standardGet(coin=coin, method=method) assert actual.URL == expected # 可选:验证父类方法是否被正确调用 mock_get_deserialize.assert_called_once_with(expected)
说明:
- 使用
@patch.object直接mock父类的方法,避免了对requests的依赖。 - 同样需要配置mock的返回值,因为
standardGet会接收父类方法的返回值。
内容的提问来源于stack exchange,提问作者Luis Felipe
相关产品推荐
相关产品推荐

