我正在尝试使用Pythons mock package来模拟Pythons requests
模块。让我在下面的场景中工作的基本要求是什么?
在我的views.py中,我有一个函数可以使每次request.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步:
验证回复包含'响应','b响应','c响应'
如何完成步骤1(模拟请求模块)?
答案 0 :(得分:187)
这是你可以做到的(你可以按原样运行这个文件):
import requests
import unittest
from unittest import mock
# This is the class we want to test
class MyGreatClass:
def fetch_json(self, url):
response = requests.get(url)
return response.json()
# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
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
if args[0] == 'http://someurl.com/test.json':
return MockResponse({"key1": "value1"}, 200)
elif args[0] == 'http://someotherurl.com/anothertest.json':
return MockResponse({"key2": "value2"}, 200)
return MockResponse(None, 404)
# Our test case class
class MyGreatClassTestCase(unittest.TestCase):
# We patch 'requests.get' with our own method. The mock object is passed in to our test case method.
@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
# Assert requests.get calls
mgc = MyGreatClass()
json_data = mgc.fetch_json('http://someurl.com/test.json')
self.assertEqual(json_data, {"key1": "value1"})
json_data = mgc.fetch_json('http://someotherurl.com/anothertest.json')
self.assertEqual(json_data, {"key2": "value2"})
json_data = mgc.fetch_json('http://nonexistenturl.com/cantfindme.json')
self.assertIsNone(json_data)
# We can even assert that our mocked method was called with the right parameters
self.assertIn(mock.call('http://someurl.com/test.json'), mock_get.call_args_list)
self.assertIn(mock.call('http://someotherurl.com/anothertest.json'), mock_get.call_args_list)
self.assertEqual(len(mock_get.call_args_list), 3)
if __name__ == '__main__':
unittest.main()
重要说明:如果您的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()
享受!
答案 1 :(得分:81)
尝试使用responses library:
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
为自己设置所有模拟提供了相当不错的便利
还有HTTPretty:
它并非特定于requests
库,在某些方面更强大,但我发现它不能很好地检查它截获的请求,responses
很容易
还有httmock。
答案 2 :(得分:35)
这对我有用:
import mock
@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
答案 3 :(得分:23)
我使用requests-mock为单独的模块编写测试:
# 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()
答案 4 :(得分:12)
这是你如何模拟requests.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
答案 5 :(得分:2)
如果你想模拟一个假响应,另一种方法是简单地实例化基本HttpResponse类的实例,如下所示:
from django.http.response import HttpResponseBase
self.fake_response = HttpResponseBase()
答案 6 :(得分:2)
我从Johannes Farhenkrug的答案here开始,这对我来说非常有用。我需要模拟请求库,因为我的目标是隔离我的应用程序,而不测试任何第三方资源。
然后,我阅读了有关python Mock库的更多信息,我意识到我可以用python Mock类代替MockResponse类,您可以将其称为“ Test Double”或“ Fake”。 / p>
这样做的好处是可以访问诸如assert_called_with
,call_args
之类的东西。不需要额外的库。诸如“可读性”或“其更具Python风格”之类的其他好处是主观的,因此它们可能对您没有作用。
这是我的版本,使用python的Mock代替了测试版进行了更新:
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链接:
https://repl.it/@abkonsta/Using-unittestMock-for-requestspost#main.py
https://repl.it/@abkonsta/Using-test-double-for-requestspost#main.py
答案 7 :(得分:1)
解决请求的一种可能方法是使用库betamax,它记录所有请求,然后,如果您在具有相同参数的相同url中发出请求,则betamax将使用记录的请求,我一直在使用它测试网络爬虫,它为我节省了很多时间。
public static void main(String... args) throws Exception {
Scanner scanner = new Scanner(System.in);
String charArr[] = new String[5];
System.out.println("Enter five chatracters: ");
// storing the characters into an array
for (int i = 0; i < 5; i++) {
charArr[i] = scanner.next();
}
// using %10s for the spaces to be appended to each character
for (int i = 0; i < 5; i++) {
System.out.printf("%10s", charArr[i]);
}
scanner.close();
}
答案 8 :(得分:1)
由于我很难弄清楚如何模拟异步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)
如果您有更好的方法,请告诉我,但我认为这样很干净。
答案 9 :(得分:1)
这对我有用,虽然我还没有做过很多复杂的测试。
import json
from requests import Response
class MockResponse(Response):
def __init__(self,
url='http://example.com',
headers={'Content-Type':'text/html; charset=UTF-8'},
status_code=200,
reason = 'Success',
_content = 'Some html goes here',
json_ = None,
encoding='UTF-8'
):
self.url = url
self.headers = headers
if json_ and headers['Content-Type'] == 'application/json':
self._content = json.dumps(json_).encode(encoding)
else:
self._content = _content.encode(encoding)
self.status_code = status_code
self.reason = reason
self.encoding = encoding
然后您可以创建回复:
mock_response = MockResponse(
headers={'Content-Type' :'application/json'},
status_code=401,
json_={'success': False},
reason='Unauthorized'
)
mock_response.raise_for_status()
给予
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://example.com
答案 10 :(得分:0)
对于仍在挣扎中的那些人来说,只是有用的提示,它们从urllib或urllib2 / urllib3转换为请求并尝试模拟响应-在实现我的模拟时,我遇到了一个令人困惑的错误:
with requests.get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:
AttributeError:__enter __
当然,如果我对with
的工作方式一无所知(我不知道),那我就会知道这是一种残余的不必要的context(摘自PEP 343) 。使用请求库时不必要,因为它对您under the hood基本上具有相同的作用。只需移除with
并使用裸露的requests.get(...)
和Bob's your uncle。
答案 11 :(得分:0)
对于 pytest 用户,有一个来自 https://pypi.org/project/pytest-responsemock/
的便利装置例如模拟 GET 到 http://some.domain,您可以:
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'