Python 如何模拟请求和响应?
我正在尝试使用模拟PythonsPython 如何模拟请求和响应?,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
请求
模块。让我在下面的场景中工作的基本要求是什么
在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'