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


当前回答

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

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。你必须模仿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()

享受吧!

其他回答

您可以使用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——文档非常棒。

目前最简单的方法:

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

为了避免安装其他依赖项,您应该创建一个假响应。这个FakeResponse可以是Response的子类(我认为这是一个很好的方法,因为它更现实),或者只是一个具有您需要的属性的简单类。

简单的假类

class FakeResponse:
        status_code = None

        def __init__(self, *args, **kwargs):
            self.status_code = 500
            self.text = ""

回应之子

class FakeResponse(Response):
        encoding = False
        _content = None

        def __init__(*args, **kwargs):
            super(FakeResponse).__thisclass__.status_code = 500
            # Requests requires to be not be None, if not throws an exception
            # For reference: https://github.com/psf/requests/issues/3698#issuecomment-261115119
            super(FakeResponse).__thisclass__.raw = io.BytesIO()

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

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

只是一个有用的提示给那些仍然挣扎,从urllib或urllib2/urllib3转换到请求,并试图模拟一个响应-我在实现我的模拟时得到了一个稍微令人困惑的错误:

与请求。get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:

AttributeError: __enter__

当然,如果我知道with如何工作(我不知道),我就会知道这是一个残留的、不必要的上下文(来自PEP 343)。在使用请求库时没有必要,因为它在底层为您做了基本相同的事情。只需删除with并使用bare requests.get(…),就万事大吉了。