我如何模拟请求和响应?

kk1*_*957 182 python mocking request

我正在尝试使用Pythons mock包来模拟Pythons requests模块.让我在以下场景中工作的基本要求是什么?

在我的views.py中,我有一个函数可以使每次request.get()调用具有不同的响应

def myview(request):
  res1 = requests.get('aurl')
  res2 = request.get('burl')
  res3 = request.get('curl')
Run Code Online (Sandbox Code Playgroud)

在我的测试类中,我想做类似的事情,但无法弄清楚确切的方法调用

步骤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'
Run Code Online (Sandbox Code Playgroud)

第2步:

打电话给我

第3步:

验证响应包含'响应','b响应','c响应'

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

Joh*_*rug 225

这是你可以这样做的(你可以按原样运行这个文件):

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()
Run Code Online (Sandbox Code Playgroud)

重要说明:如果您的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()
Run Code Online (Sandbox Code Playgroud)

请享用!

  • 在Python 2.x中,只需将`from unittest import mock`替换为`import mock`,其余工作原样.你需要单独安装`mock`包. (9认同)
  • 太棒了.由于在Python 3中返回迭代器的改变,我不得不在Python 3中稍微改变一下`mock_requests_get`需要`yield`而不是`return`. (3认同)
  • MockResponse课是个好主意!我试图伪造一个resuests.Response类对象,但这并不容易.我可以使用这个MockResponse代替真实的东西.谢谢! (2认同)
  • 该解决方案非常适合 GET 请求。我试图将其推广到 POST 和 PUT,但无法理解如何提供在“mocked_requests_get”内部使用的额外数据。`mocked_requests_get` 中的所有输入参数都将在请求中使用。有没有办法添加更多参数,以便它们不在请求本身中使用,而仅用于请求之前的数据操作? (2认同)

Ane*_*pic 106

尝试使用响应库:

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"}'
Run Code Online (Sandbox Code Playgroud)

为自己设置所有模拟提供了相当不错的便利

还有HTTPretty:

它不是特定于requests库,在某些方面更强大,虽然我发现它不能很好地检查它截获的请求,这responses很容易

httmock:

  • 在这里回复开发人员。我们在 pytest 上运行所有单元测试,没有任何问题。如果您遇到任何问题,请在 GitHub 上提交 (4认同)
  • @scubbo 你可以传递一个预编译的正则表达式作为 url 参数并使用回调样式 https://github.com/getsentry/responses#dynamic-responses 这会给你我认为你想要的通配符行为(可以访问传递的回调函数收到的`request` arg 上的url) (3认同)
  • 太棒了 (2认同)

kk1*_*957 44

这对我有用:

import mock
@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
Run Code Online (Sandbox Code Playgroud)

  • 对于 Python 3,使用 `from unittest import mock`。https://docs.python.org/3/library/unittest.mock.html (8认同)
  • 如果您期望text / html响应,那么它将起作用。如果您要模拟REST API,要检查状态代码等,那么Johannes [/sf/answers/1995546451/]的答案可能是解决方法。 (2认同)

Ana*_*ana 28

我使用requests-mock为单独的模块编写测试:

# module.py
import requests

class A():

    def get_response(self, url):
        response = requests.get(url)
        return response.text
Run Code Online (Sandbox Code Playgroud)

测试:

# 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()
Run Code Online (Sandbox Code Playgroud)


小智 15

这是带有请求响应类的解决方案。恕我直言,它更清洁。

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 ...
Run Code Online (Sandbox Code Playgroud)

  • 我真的不喜欢使用 `_content` 因为它是一个内部方法,但是尝试通过 `raw` 属性设置内容是相当麻烦的,所以这是我发现的获得真正的 `Response` 对象的最佳方法修补后的“requests.get”返回值。 (2认同)

小智 13

这是你如何模拟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
Run Code Online (Sandbox Code Playgroud)

  • 或者`mockresponse.json.return_value = {"key": "value"}` (3认同)
  • 如果我想模拟一个函数怎么办?例如如何模拟:mockresponse.json() = {"key": "value"} (2认同)
  • @primoz,我为此使用了一个匿名函数/lambda:`mockresponse.json = lambda: {'key': 'value'}` (2认同)

Tom*_*pin 6

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

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()
Run Code Online (Sandbox Code Playgroud)

  • 这就是我试图找到的答案:获取一个假的 django 响应对象,它可以通过中间件的范围进行几乎 e2e 测试。不过,“HttpResponse”,而不是 ...Base,为我解决了这个问题。谢谢! (3认同)

abk*_*sta 6

我从Johannes Farhenkrug的回答开始,它对我很有用。我需要模拟 requests 库,因为我的目标是隔离我的应用程序而不是测试任何 3rd 方资源。

然后我阅读了一些关于 python 的Mock库的更多信息,我意识到我可以用 python Mock 类替换 MockResponse 类,你可能称之为“Test Double”或“Fake”。

这样做的好处是可以访问诸如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')

Run Code Online (Sandbox Code Playgroud)

复制链接:

https://repl.it/@abkonsta/Using-unittestMock-for-requestspost#main.py

https://repl.it/@abkonsta/Using-test-double-for-requestspost#main.py


use*_*881 5

尽管我还没有做过太多复杂的测试,但这对我有用。

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
Run Code Online (Sandbox Code Playgroud)

然后您可以创建响应:

mock_response = MockResponse(
    headers={'Content-Type' :'application/json'},
    status_code=401,
    json_={'success': False},
    reason='Unauthorized'
)
mock_response.raise_for_status()
Run Code Online (Sandbox Code Playgroud)

给出

requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://example.com
Run Code Online (Sandbox Code Playgroud)


Jac*_*eth 5

你可以使用requests-mock代替吗?

假设您的 myview 函数接受一个requests.Session对象,用它发出请求,并对输出执行某些操作:

# mypackage.py
def myview(session):
    res1 = session.get("http://aurl")
    res2 = session.get("http://burl")
    res3 = session.get("http://curl")
    return f"{res1.text}, {res2.text}, {res3.text}"
Run Code Online (Sandbox Code Playgroud)
# test_myview.py
from mypackage import myview
import requests

def test_myview(requests_mock):
    # set up requests
    a_req = requests_mock.get("http://aurl", text="a response")
    b_req = requests_mock.get("http://burl", text="b response")
    c_req = requests_mock.get("http://curl", text="c response")

    # test myview behaviour
    session = requests.Session()
    assert myview(session) == "a response, b response, c response"

    # check that requests weren't called repeatedly
    assert a_req.called_once
    assert b_req.called_once
    assert c_req.called_once
    assert requests_mock.call_count == 3
Run Code Online (Sandbox Code Playgroud)

您还可以使用requests_mockPytest 以外的框架 - 文档很棒。