我试图使用python模拟包来模拟python请求模块。让我在下面的场景中工作的基本调用是什么?
在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:
验证响应包含'a response', 'b response', 'c response'
我如何完成第1步(模拟请求模块)?
目前最简单的方法:
from unittest import TestCase
from unittest.mock import Mock, patch
from .utils import method_foo
class TestFoo(TestCase):
@patch.object(utils_requests, "post") # change to desired method here
def test_foo(self, mock_requests_post):
# EXPLANATION: mocked 'post' method above will return some built-in mock,
# and its method 'json' will return mock 'mock_data',
# which got argument 'return_value' with our data to be returned
mock_data = Mock(return_value=[{"id": 1}, {"id": 2}])
mock_requests_post.return_value.json = mock_data
method_foo()
# TODO: asserts here
"""
Example of method that you can test in utils.py
"""
def method_foo():
response = requests.post("http://example.com")
records = response.json()
for record in records:
print(record.get("id"))
# do other stuff here
我将演示如何通过将真正的请求与返回相同数据的假请求交换来将编程逻辑与实际的外部库分离。在你看来,如果外部api调用,那么这个过程是最好的
import pytest
from unittest.mock import patch
from django.test import RequestFactory
@patch("path(projectname.appname.filename).requests.post")
def test_mock_response(self, mock_get, rf: RequestFactory):
mock_get.return_value.ok = Mock(ok=True)
mock_get.return_value.status_code = 400
mock_get.return_value.json.return_value = {you can define here dummy response}
request = rf.post("test/", data=self.payload)
response = view_name_view(request)
expected_response = {
"success": False,
"status": "unsuccessful",
}
assert response.data == expected_response
assert response.status_code == 400
我从Johannes Farhenkrug的答案开始,它对我很有效。我需要模拟请求库,因为我的目标是隔离我的应用程序,不测试任何第三方资源。
然后我读了更多关于python的Mock库,我意识到我可以用python Mock类替换MockResponse类,你可能称之为“Test Double”或“Fake”。
这样做的好处是可以访问assert_called_with, call_args等。不需要额外的库。额外的好处,如“可读性”或“更python化”是主观的,所以它们可能对你有影响,也可能没有。
这是我的版本,更新使用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。它的链接:
https://repl.it/@abkonsta/Using-unittestMock-for-requestspost#main.py
https://repl.it/@abkonsta/Using-test-double-for-requestspost#main.py
尝试使用响应库。以下是他们文档中的一个例子:
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"}'
相比于自己设置所有的mock,它提供了相当好的便利。
还有HTTPretty:
它不是特定于请求库,在某些方面更强大,尽管我发现它不太适合检查它拦截的请求,而响应则很容易
还有httmock。
最近,一个比古老的请求更受欢迎的新库是httpx,它增加了对异步的一等支持。httpx的模拟库是:https://github.com/lundberg/respx
解决请求的一个可能的方法是使用库betamax,它记录所有的请求,之后如果你在相同的url中使用相同的参数发出请求,betamax将使用记录的请求,我一直在用它来测试网络爬虫,它节省了我很多时间。
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')
https://betamax.readthedocs.io/en/latest/