Làm thế nào tôi có thể chế giễu các yêu cầu và phản hồi?


221

Tôi đang cố gắng sử dụng gói giả của Pythons để mô phỏng requestsmô-đun Pythons . Các cuộc gọi cơ bản để giúp tôi làm việc trong kịch bản dưới đây là gì?

Trong lượt xem của tôi, tôi có một chức năng thực hiện nhiều cuộc gọi request.get () với các phản hồi khác nhau mỗi lần

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

Trong lớp thử nghiệm của tôi, tôi muốn làm một cái gì đó như thế này nhưng không thể tìm ra các cuộc gọi phương thức chính xác

Bước 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'

Bước 2:

Gọi cho quan điểm của tôi

Bước 3:

xác minh phản hồi chứa 'phản hồi', 'b phản hồi', 'c phản hồi'

Làm cách nào tôi có thể hoàn thành Bước 1 (mô phỏng mô-đun yêu cầu)?


5
Đây là liên kết hoạt động cra.mr/2014/05/20/mocking-requests-with-responses
Yogesh lele

Câu trả lời:


277

Đây là cách bạn có thể làm điều đó (bạn có thể chạy tệp này nguyên trạng):

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

Lưu ý quan trọng: Nếu MyGreatClasslớp của bạn sống trong một gói khác, giả sử my.great.package, bạn phải chế giễu my.great.package.requests.getthay vì chỉ 'request.get'. Trong trường hợp đó, trường hợp thử nghiệm của bạn sẽ như thế này:

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

Thưởng thức!


2
Lớp MockResponse là một ý tưởng tuyệt vời! Tôi đã cố gắng giả mạo một đối tượng resuests.Response nhưng nó không dễ dàng. Tôi có thể sử dụng MockResponse này thay cho vật thật. Cảm ơn bạn!
yoshi

@yoshi Vâng, tôi phải mất một thời gian để quấn đầu xung quanh những kẻ nhạo báng trong Python nhưng điều này hoạt động khá tốt với tôi!
Julian Fahrenkrug

10
Và trong Python 2.x, chỉ cần thay thế from unittest import mockbằng import mockvà phần còn lại hoạt động như bình thường. Bạn cần phải cài đặt mockgói riêng.
haridsv

3
Tuyệt diệu. Tôi đã phải thực hiện một thay đổi nhỏ trong Python 3 khi mock_requests_getcần yieldthay returnvì thay đổi để trả về các trình vòng lặp trong Python 3.
xóa

1
đó là những gì câu hỏi ban đầu được hỏi về. Tôi đã tìm ra cách (đóng gói ứng dụng vào gói và cố định test_client () để thực hiện cuộc gọi). cảm ơn vì bài viết mặc dù vẫn sử dụng xương sống của mã.
Bunny tự tử

141

Hãy thử sử dụng thư viện phản hồi :

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

cung cấp khá tiện lợi hơn là tự mình thiết lập tất cả các chế giễu

Ngoài ra còn có HTTPretty :

Nó không đặc trưng cho requeststhư viện, mạnh hơn theo một số cách mặc dù tôi thấy nó không cho vay rất tốt để kiểm tra các yêu cầu mà nó đã chặn, điều nàyresponses này khá dễ dàng

Ngoài ra còn có omemock .


Nhìn thoáng qua, tôi không thấy cách nào responsesđể khớp với url ký tự đại diện - nghĩa là triển khai logic gọi lại như "lấy phần cuối của url, tra cứu nó trong Bản đồ và trả về giá trị tương ứng". Điều đó là có thể, và tôi chỉ thiếu nó?
scubbo

1
@scubbo bạn có thể vượt qua regex được biên dịch sẵn dưới dạng url param và sử dụng kiểu gọi lại github.com/getsentry/responses#dynamic-responses, điều này sẽ cung cấp cho bạn hành vi ký tự đại diện mà bạn muốn (tôi có thể truy cập url đã qua trên requestarg nhận được bởi func gọi lại)
Anentropic

48

Đây là những gì làm việc cho tôi:

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

3
Điều này sẽ hoạt động nếu bạn đang mong đợi phản hồi văn bản / html. Nếu bạn đang chế giễu một API REST, muốn kiểm tra mã trạng thái, v.v. thì câu trả lời từ Johannes [ stackoverflow.com/a/28507806/3559967] có lẽ là cách tốt nhất.
Antony

5
Đối với Python 3, sử dụng from unittest import mock. docs.python.org/3/l Library / unittest.mock.html
phoenix

32

Tôi đã sử dụng request-mock để viết bài kiểm tra cho mô-đun riêng biệt:

# module.py
import requests

class A():

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

Và các bài kiểm tra:

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

Nơi nào bạn nhận được m in '(tự, m):'
Denis Evseev

16

đây là cách bạn giả định request.post, thay đổi nó thành phương thức http của bạn

@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

1
Nếu tôi muốn chế giễu một chức năng thì sao? Cách giả định điều này chẳng hạn: mockresponse.json () = {"key": "value"}
primoz

1
@primoz, tôi đã sử dụng một hàm ẩn danh / lambda cho điều đó:mockresponse.json = lambda: {'key': 'value'}
Tayler

1
Hoặcmockresponse.json.return_value = {"key": "value"}
Lars Blumberg

5

Nếu bạn muốn chế nhạo một phản hồi giả, một cách khác để làm điều đó là chỉ cần khởi tạo một thể hiện của lớp HttpResponse cơ bản, như vậy:

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

Đây là câu trả lời cho những gì tôi đã cố gắng tìm: lấy một đối tượng phản hồi django giả có thể làm cho nó thông qua gam màu của phần mềm trung gian cho một bài kiểm tra gần như e2e. HttpResponse, thay vì ... Base, đã lừa tôi. Cảm ơn!
low_ghost

4

Một cách khả thi để giải quyết các yêu cầu là sử dụng betamax thư viện, nó ghi lại tất cả các yêu cầu và sau đó nếu bạn thực hiện một yêu cầu trong cùng một url với cùng tham số thì betamax sẽ sử dụng yêu cầu được ghi lại, tôi đã sử dụng nó để kiểm tra trình thu thập dữ liệu web và nó giúp tôi tiết kiệm rất nhiều thời gian.

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/


Lưu ý rằng betamax được thiết kế để chỉ hoạt động với các yêu cầu , nếu bạn cần nắm bắt các yêu cầu HTTP đã khiến người dùng API HTTP cấp thấp hơn như omeplib3 hoặc với aiohttp thay thế hoặc các ứng dụng khách như boto, sử dụng vcrpy thay vì hoạt động ở cấp thấp hơn. Thông tin khác tại github.com/betamaxpy/betamax/issues/125
Le Hibou

0

Chỉ là một gợi ý hữu ích cho những người vẫn đang gặp khó khăn, chuyển đổi từ urllib hoặc urllib2 / urllib3 thành các yêu cầu VÀ cố gắng chế giễu một phản hồi- Tôi đã nhận được một lỗi hơi khó hiểu khi thực hiện giả của mình:

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

Thuộc tínhError: __enter__

Chà, tất nhiên, nếu tôi biết bất cứ điều gì về cách thức withhoạt động (tôi đã không), tôi sẽ biết đó là một bối cảnh không cần thiết (từ PEP 343 ). Không cần thiết khi sử dụng thư viện yêu cầu vì về cơ bản nó cũng tương tự đối với bạn dưới mui xe . Chỉ cần loại bỏ withvà sử dụng trần requests.get(...)Bob là chú của bạn .


0

Tôi sẽ thêm thông tin này vì tôi đã có một thời gian khó khăn để tìm cách giả lập một cuộc gọi api không đồng bộ.

Đây là những gì tôi đã làm để chế nhạo một cuộc gọi không đồng bộ.

Đây là chức năng tôi muốn kiểm tra

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

Bạn vẫn cần lớp 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

Bạn thêm lớp MockResponseAsync

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

    async def getResponse(self):
        return self.response

Đây là bài kiểm tra. Điều quan trọng ở đây là tôi tạo phản hồi trước vì hàm init không thể không đồng bộ và lệnh gọi getResponse là không đồng bộ để tất cả được kiểm tra.

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

Nếu bạn có cách làm tốt hơn nói với tôi nhưng tôi nghĩ nó khá sạch sẽ như thế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.