Python 如何模拟请求和响应?

Python 如何模拟请求和响应?,python,mocking,request,Python,Mocking,Request,我正在尝试使用模拟Pythons请求模块。让我在下面的场景中工作的基本要求是什么 在my views.py中,我有一个函数,它可以发出各种各样的请求。get()调用,每次都有不同的响应 def myview(request): res1 = requests.get('aurl') res2 = request.get('burl') res3 = request.get('curl') 在我的测试类中,我想做类似的事情,但无法找出确切的方法调用 步骤1: # Mock the r

我正在尝试使用模拟Pythons
请求
模块。让我在下面的场景中工作的基本要求是什么

在my views.py中,我有一个函数,它可以发出各种各样的请求。get()调用,每次都有不同的响应

def myview(request):
  res1 = requests.get('aurl')
  res2 = request.get('burl')
  res3 = request.get('curl')
在我的测试类中,我想做类似的事情,但无法找出确切的方法调用

步骤1:

# Mock the requests module
# when mockedRequests.get('aurl') is called then return 'a response'
# when mockedRequests.get('burl') is called then return 'b response'
# when mockedRequests.get('curl') is called then return 'c response'
步骤2:

叫我的观点

步骤3:

验证响应是否包含“a响应”、“b响应”、“c响应”


如何完成步骤1(模拟请求模块)?

以下是对我有效的方法:

import mock
@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
尝试使用。以下是一个例子:

导入响应
导入请求
@响应。激活
def test_simple():
responses.add(responses.GET,'http://twitter.com/api/1/foobar',
json={'error':'notfound'},状态=404)
resp=requests.get('http://twitter.com/api/1/foobar')
assert resp.json()=={“错误”:“未找到”}
断言len(responses.calls)==1
断言响应。调用[0]。请求。url='http://twitter.com/api/1/foobar'
断言响应。调用[0]。响应.text='{“错误”:“未找到”}
它提供了一个很好的方便,超过了设置所有的自嘲

还有:

它并不特定于
请求
库,在某些方面更强大,尽管我发现它不太适合检查它拦截的请求,而
响应
非常容易

还有。

您可以这样做(您可以按原样运行此文件):

重要提示:如果您的
MyGreatClass
类生活在不同的包中,请说
my.great.package
,您必须模拟
my.great.package.requests.get
,而不仅仅是“request.get”。在这种情况下,您的测试用例如下所示:

import unittest
from unittest import mock
from my.great.package import MyGreatClass

# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
    # Same as above


class MyGreatClassTestCase(unittest.TestCase):

    # Now we must patch 'my.great.package.requests.get'
    @mock.patch('my.great.package.requests.get', side_effect=mocked_requests_get)
    def test_fetch(self, mock_get):
        # Same as above

if __name__ == '__main__':
    unittest.main()
享受吧

I用于编写单独模块的测试:

# module.py
import requests

class A():

    def get_response(self, url):
        response = requests.get(url)
        return response.text
以及测试:

# tests.py
import requests_mock
import unittest

from module import A


class TestAPI(unittest.TestCase):

    @requests_mock.mock()
    def test_get_response(self, m):
        a = A()
        m.get('http://aurl.com', text='a response')
        self.assertEqual(a.get_response('http://aurl.com'), 'a response')
        m.get('http://burl.com', text='b response')
        self.assertEqual(a.get_response('http://burl.com'), 'b response')
        m.get('http://curl.com', text='c response')
        self.assertEqual(a.get_response('http://curl.com'), 'c response')

if __name__ == '__main__':
    unittest.main()

如果要模拟假响应,另一种方法是简单地实例化基本HttpResponse类的实例,如下所示:

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

这就是模拟请求的方式。post,将其更改为http方法

@patch.object(requests, 'post')
def your_test_method(self, mockpost):
    mockresponse = Mock()
    mockpost.return_value = mockresponse
    mockresponse.text = 'mock return'

    #call your target method now

解决请求的一种可能方法是使用betamax库,它记录所有请求,之后如果您在相同的url中使用相同的参数发出请求,betamax将使用记录的请求,我一直在使用它测试web爬虫,这为我节省了很多时间

import os

import requests
from betamax import Betamax
from betamax_serializers import pretty_json


WORKERS_DIR = os.path.dirname(os.path.abspath(__file__))
CASSETTES_DIR = os.path.join(WORKERS_DIR, u'resources', u'cassettes')
MATCH_REQUESTS_ON = [u'method', u'uri', u'path', u'query']

Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
with Betamax.configure() as config:
    config.cassette_library_dir = CASSETTES_DIR
    config.default_cassette_options[u'serialize_with'] = u'prettyjson'
    config.default_cassette_options[u'match_requests_on'] = MATCH_REQUESTS_ON
    config.default_cassette_options[u'preserve_exact_body_bytes'] = True


class WorkerCertidaoTRT2:
    session = requests.session()

    def make_request(self, input_json):
        with Betamax(self.session) as vcr:
            vcr.use_cassette(u'google')
            response = session.get('http://www.google.com')

对于那些仍在挣扎的人来说,这只是一个有用的提示,他们正在从urllib或urllib2/urllib3转换为请求并试图模拟响应-在实现模拟时,我遇到了一个稍微令人困惑的错误:

with requests.get(路径,auth=HTTPBasicAuth('user','pass'),verify=False)作为url:

AttributeError:\u输入__


当然,如果我知道
如何使用
的话(我不知道),我会知道这是一种残留的、不必要的(来自)。使用requests库时不需要,因为它对您的作用基本相同。只需使用
删除
,并使用裸
请求。获取(…)
和。

我将添加此信息,因为我很难弄清楚如何模拟异步api调用

下面是我模拟异步调用所做的

这是我想要测试的函数

async def get_user_info(headers, payload):
    return await httpx.AsyncClient().post(URI, json=payload, headers=headers)
您仍然需要MockResponse类

class MockResponse:
    def __init__(self, json_data, status_code):
        self.json_data = json_data
        self.status_code = status_code

    def json(self):
        return self.json_data
添加MockResponseAsync类

class MockResponseAsync:
    def __init__(self, json_data, status_code):
        self.response = MockResponse(json_data, status_code)

    async def getResponse(self):
        return self.response
这是测试。这里重要的是我之前创建了响应,因为init函数不能是异步的,对getResponse的调用是异步的,所以它都被签出了

@pytest.mark.asyncio
@patch('httpx.AsyncClient')
async def test_get_user_info_valid(self, mock_post):
    """test_get_user_info_valid"""
    # Given
    token_bd = "abc"
    username = "bob"
    payload = {
        'USERNAME': username,
        'DBNAME': 'TEST'
    }
    headers = {
        'Authorization': 'Bearer ' + token_bd,
        'Content-Type': 'application/json'
    }
    async_response = MockResponseAsync("", 200)
    mock_post.return_value.post.return_value = async_response.getResponse()

    # When
    await api_bd.get_user_info(headers, payload)

    # Then
    mock_post.return_value.post.assert_called_once_with(
        URI, json=payload, headers=headers)
如果你有更好的方法,请告诉我,但我认为这样做很简单。

我从的答案开始,它对我很有效。我需要模拟请求库,因为我的目标是隔离我的应用程序,而不是测试任何第三方资源

然后我阅读了更多关于python库的内容,我意识到我可以用python模拟类替换MockResponse类,您可以称之为“Test Double”或“Fake”

这样做的好处是可以访问
assert\u、call\u args
等内容。不需要额外的库。额外的好处,如“可读性”或“其更具pythonic”是主观的,因此它们可能会也可能不会对你起作用

这是我的版本,使用python的Mock而不是test double进行了更新:

import json
import requests
from unittest import mock

# defube stubs
AUTH_TOKEN = '{"prop": "value"}'
LIST_OF_WIDGETS = '{"widgets": ["widget1", "widget2"]}'
PURCHASED_WIDGETS = '{"widgets": ["purchased_widget"]}'


# exception class when an unknown URL is mocked
class MockNotSupported(Exception):
  pass


# factory method that cranks out the Mocks
def mock_requests_factory(response_stub: str, status_code: int = 200):
    return mock.Mock(**{
        'json.return_value': json.loads(response_stub),
        'text.return_value': response_stub,
        'status_code': status_code,
        'ok': status_code == 200
    })


# side effect mock function
def mock_requests_post(*args, **kwargs):
    if args[0].endswith('/api/v1/get_auth_token'):
        return mock_requests_factory(AUTH_TOKEN)
    elif args[0].endswith('/api/v1/get_widgets'):
        return mock_requests_factory(LIST_OF_WIDGETS)
    elif args[0].endswith('/api/v1/purchased_widgets'):
        return mock_requests_factory(PURCHASED_WIDGETS)
    
    raise MockNotSupported


# patch requests.post and run tests
with mock.patch('requests.post') as requests_post_mock:
  requests_post_mock.side_effect = mock_requests_post
  response = requests.post('https://myserver/api/v1/get_widgets')
  assert response.ok is True
  assert response.status_code == 200
  assert 'widgets' in response.json()
  
  # now I can also do this
  requests_post_mock.assert_called_with('https://myserver/api/v1/get_widgets')

Repl.it链接:


这是一个带有请求-响应类的解决方案。它更干净

import json
from unittest.mock import patch
from requests.models import Response

def mocked_request_get(*args, **kwargs):
    response_content = None
    request_url = kwargs.get('url', None)
    if request_url == 'aurl':
        response_content = json.dumps('a response')
    elif request_url == 'burl':
        response_content = json.dumps('b response')
    elif request_url == 'curl':
        response_content = json.dumps('c response')
    response = Response()
    response.status_code = 200
    response._content = str.encode(response_content)
    return response

@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
     response = call_your_view()
     assert ...

对于pytest用户,有一个来自

例如,要模拟访问,您可以:


MockResponse类是一个好主意!我试图伪造ResTests.Response类对象,但这并不容易。我可以用这个模拟的反应来代替真实的反应。谢谢大家!@yoshi是的,我花了一段时间才学会用Python编写mock,但这对我来说非常有效!在Python2.x中,只需将unittest import mock中的
替换为
import mock
,其余部分就可以正常工作了。您确实需要单独安装
mock
软件包。我不得不在Python3中做一点小小的更改,因为Python3中返回迭代器的更改需要
mock_-requests\u-get
yield
,而不是
return
。这就是问题最初要问的。我已经找到了方法(将应用程序打包到包中,并固定一个test_client()来执行调用)。不过,感谢您的帖子,我仍然在使用代码的主干。如果您需要文本/html响应,这将起作用。如果您正在模拟REST API,希望检查状态代码等。那么Johannes的答案[可能是正确的。对于Python 3,使用unittest import mock的
。如果我想模拟函数呢?如何模拟例如:mockresponse.json()={“key”:“value”}@primoz,我使用了匿名函数/lambda:
mock
import json
from unittest.mock import patch
from requests.models import Response

def mocked_request_get(*args, **kwargs):
    response_content = None
    request_url = kwargs.get('url', None)
    if request_url == 'aurl':
        response_content = json.dumps('a response')
    elif request_url == 'burl':
        response_content = json.dumps('b response')
    elif request_url == 'curl':
        response_content = json.dumps('c response')
    response = Response()
    response.status_code = 200
    response._content = str.encode(response_content)
    return response

@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
     response = call_your_view()
     assert ...
def test_me(response_mock):

    with response_mock('GET http://some.domain -> 200 :Nice'):
        response = send_request()
        assert result.ok
        assert result.content == b'Nice'