我试图使用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步(模拟请求模块)?


当前回答

使用requests_mock可以很容易地修补任何请求

pip install requests-mock
from unittest import TestCase
import requests_mock
from <yourmodule> import <method> (auth)

class TestApi(TestCase):
  @requests_mock.Mocker()
  def test_01_authentication(self, m):
        """Successful authentication using username password"""
        token = 'token'
        m.post(f'http://localhost/auth', json= {'token': token})
        act_token =auth("user", "pass")
        self.assertEqual(act_token, token)

其他回答

我从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 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

目前最简单的方法:

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

您可以使用request -mock代替吗?

假设你的myview函数接受一个请求。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}"
# 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

你也可以在Pytest之外的框架中使用requests_mock——文档非常棒。

对于pytest用户,有一个来自https://pypi.org/project/pytest-responsemock/的方便的fixture

例如,模拟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'