You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构造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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:55:13