如何构造http.client.HTTPResponse实例以测试自定义API方法?
问题描述
对接第三方API时,使用requests或urllib.request发送请求均失败,但用http.client能正常获取响应。为此编写了如下API类:
import http.client import json class API: def get_response_data(self, response: http.client.HTTPResponse) -> dict: """Get the response data.""" response_body = response.read() response_data = json.loads(response_body.decode("utf-8")) return response_data
实际使用流程正常,但编写测试用例时,无法直接构造包含预期输出的http.client.HTTPResponse实例(官方文档说明该类不能由用户直接实例化,构造函数需要传入sock参数),希望实现对应的单元测试。
解决方案
方法1:用unittest.mock模拟HTTPResponse(最简单)
我们只用到HTTPResponse的read()方法,因此可以用Mock创建一个符合接口的模拟对象,无需处理socket细节:
from . import API from unittest import TestCase, mock import json class APITestCase(TestCase): def setUp(self) -> None: super().setUp() self.api = API() def test_get_response_data_returns_expected_data(self) -> None: expected_response_data = {"token": "a_secret_token"} # 模拟HTTPResponse对象,让read()返回序列化后的字节数据 mock_response = mock.Mock(spec=http.client.HTTPResponse) mock_response.read.return_value = json.dumps(expected_response_data).encode("utf-8") result = self.api.get_response_data(mock_response) self.assertEqual(result, expected_response_data)
方法2:用BytesIO模拟socket创建真实HTTPResponse
如果需要创建真实的HTTPResponse实例,可以自定义一个模拟socket类,用BytesIO模拟响应数据流:
from . import API from unittest import TestCase import http.client import json from io import BytesIO class MockSocket: def __init__(self, response_data): # 将预期数据转为字节流 self.data = BytesIO(json.dumps(response_data).encode("utf-8")) def makefile(self, mode, *args, **kwargs): # 返回供HTTPResponse读取的文件对象 return self.data class APITestCase(TestCase): def setUp(self) -> None: super().setUp() self.api = API() def test_get_response_data_with_real_http_response(self) -> None: expected_response_data = {"token": "a_secret_token"} # 创建模拟socket mock_sock = MockSocket(expected_response_data) # 实例化HTTPResponse,传入模拟socket及必要参数 response = http.client.HTTPResponse(mock_sock, method="POST") # 必须调用begin()完成响应初始化 response.begin() result = self.api.get_response_data(response) self.assertEqual(result, expected_response_data)
方法3:重构代码降低耦合(推荐)
get_response_data的核心逻辑是解析字节流为JSON字典,没必要强依赖HTTPResponse类型。重构后代码更灵活,测试更简单:
# 重构后的API类 import json class API: def get_response_data(self, readable) -> dict: """从实现了read()方法的对象中读取并解析响应数据""" response_body = readable.read() return json.loads(response_body.decode("utf-8")) # 可选:新增直接解析字节数据的方法 def parse_response_bytes(self, response_bytes: bytes) -> dict: """直接解析字节数据为JSON字典""" return json.loads(response_bytes.decode("utf-8"))
对应的测试代码:
from . import API from unittest import TestCase import json class APITestCase(TestCase): def setUp(self) -> None: super().setUp() self.api = API() def test_get_response_data(self) -> None: expected = {"token": "a_secret_token"} # 用简单类模拟可读对象 mock_readable = type('MockReadable', (), { 'read': lambda self: json.dumps(expected).encode('utf-8') })() self.assertEqual(self.api.get_response_data(mock_readable), expected) def test_parse_response_bytes(self) -> None: expected = {"token": "a_secret_token"} data_bytes = json.dumps(expected).encode('utf-8') self.assertEqual(self.api.parse_response_bytes(data_bytes), expected)
内容的提问来源于stack exchange,提问作者Edchel Stephen Nini
相关产品推荐
相关产品推荐

