Python 如何模拟请求和响应?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15753390/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 20:54:23  来源:igfitidea点击:

How can I mock requests and the response?

pythonmockingrequest

提问by kk1957

I am trying to use Pythons mock packageto mock Pythons requestsmodule. What are the basic calls to get me working in below scenario?

我正在尝试使用Pythons 模拟包来模拟 Pythonsrequests模块。让我在以下场景中工作的基本调用是什么?

In my views.py, I have a function that makes variety of requests.get() calls with different response each time

在我的 views.py 中,我有一个函数,它每次都会以不同的响应进行各种 requests.get() 调用

def myview(request):
  res1 = requests.get('aurl')
  res2 = request.get('burl')
  res3 = request.get('curl')

In my test class I want to do something like this but cannot figure out exact method calls

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

Step 1:

第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'

Step 2:

第2步:

Call my view

呼唤我的观点

Step 3:

第 3 步:

verify response contains 'a response', 'b response' , 'c response'

验证响应包含“a 响应”、“b 响应”、“c 响应”

How can I complete Step 1 (mocking the requests module)?

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

采纳答案by kk1957

Here is what worked for me:

这是对我有用的:

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

回答by Anentropic

Try using the responses library:

尝试使用响应库

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"}'

provides quite a nice convenience over setting up all the mocking yourself

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

There's also HTTPretty:

还有HTTPretty

It's not specific to requestslibrary, more powerful in some ways though I found it doesn't lend itself so well to inspecting the requests that it intercepted, which responsesdoes quite easily

它不是特定于requests库的,在某些方面更强大,尽管我发现它不太适合检查它拦截的请求,这responses很容易

There's also httmock.

还有httmock

回答by Johannes Fahrenkrug

This is how you can do it (you can run this file as-is):

这就是您的方法(您可以按原样运行此文件):

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

Important Note:If your MyGreatClassclass lives in a different package, say my.great.package, you have to mock my.great.package.requests.getinstead of just 'request.get'. In that case your test case would look like this:

重要提示:如果您的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()

Enjoy!

享受!

回答by AnaPana

I used requests-mockfor writing tests for separate module:

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

# module.py
import requests

class A():

    def get_response(self, url):
        response = requests.get(url)
        return response.text

And the tests:

和测试:

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

回答by Tom Chapin

If you want to mock a fake response, another way to do it is to simply instantiate an instance of the base HttpResponse class, like so:

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

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

回答by tingyiy

this is how you mock requests.post, change it to your http method

这就是您模拟 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

回答by Ronald Theodoro

One possible way to work around requests is using the library betamax, it records all requests and after that if you make a request in the same url with the same parameters the betamax will use the recorded request, I have been using it to test web crawler and it save me a lot time.

解决请求的一种可能方法是使用库 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/

https://betamax.readthedocs.io/en/latest/

回答by Max P Magee

Just a helpful hint to those that are still struggling, converting from urllib or urllib2/urllib3 to requests AND trying to mock a response- I was getting a slightly confusing error when implementing my mock:

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

with requests.get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:

with requests.get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:

AttributeError: __enter__

属性错误:__输入__

Well, of course, if I knew anything about how withworks (I didn't), I'd know it was a vestigial, unnecessary context(from PEP 343). Unnecessary when using the requests library because it does basically the same thing for you under the hood. Just remove the withand use bare requests.get(...)and Bob's your uncle.

好吧,当然,如果我对with工作原理有所了解(我不知道),我就会知道这是一个残留的、不必要的上下文(来自PEP 343)。不必要的使用请求库时,因为它基本上给你同样的事情引擎盖下。只需删除with并使用裸requests.get(...)鲍勃是你的叔叔

回答by Martbob

I will add this information since I had a hard time figuring how to mock an async api call.

我将添加此信息,因为我很难弄清楚如何模拟异步 api 调用。

Here is what I did to mock an async call.

这是我为模拟异步调用所做的工作。

Here is the function I wanted to test

这是我想测试的功能

async def get_user_info(headers, payload):
    return await httpx.AsyncClient().post(URI, json=payload, headers=headers)

You still need the MockResponse class

你仍然需要 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

You add the MockResponseAsync class

您添加 MockResponseAsync 类

class MockResponseAsync:
    def __init__(self, json_data, status_code):
        self.response = MockResponse(json_data, status_code)

    async def getResponse(self):
        return self.response

Here is the test. The important thing here is I create the response before since initfunction can't be async and the call to getResponse is async so it all checked out.

这是测试。这里重要的是我之前创建了响应,因为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)

If you have a better way of doing that tell me but I think it's pretty clean like that.

如果你有更好的方法告诉我,但我认为这样很干净。