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


当前回答

如果使用pytest:

>>> import pytest
>>> import requests

>>> def test_url(requests_mock):
...     requests_mock.get('http://test.com', text='data')
...     assert 'data' == requests.get('http://test.com').text

摘自官方文件

其他回答

我使用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()

使用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)

下面是一个带有请求响应类的解决方案。恕我直言,它更干净。

import json
from unittest.mock import patch
from requests.models import Response

def mocked_requests_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 = requests.get(url='aurl')
     assert ...

我将添加这些信息,因为我很难弄清楚如何模拟异步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)

如果你有更好的方法告诉我,不过我觉得这样很干净。

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

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()

享受吧!