310

Pythons mock パッケージを使用して Pythonsrequestsモジュールをモックしようとしています。以下のシナリオで作業するための基本的な呼び出しは何ですか?

私のviews.pyには、毎回異なる応答でさまざまな requests.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 応答」、「b 応答」、「c 応答」が含まれていることを確認します

ステップ 1 (要求モジュールのモック) を完了するにはどうすればよいですか?

4

16 に答える 16

375

これを行う方法は次のとおりです(このファイルをそのまま実行できます):

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クラスが別のパッケージに存在する場合、たとえば、「request.get」だけでなくmy.great.packageモックを作成する必要があります。my.great.package.requests.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()

楽しみ!

于 2015-02-13T20:00:50.960 に答える
42

別のモジュールのテストを作成するために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()
于 2015-05-08T20:43:22.237 に答える
7

Johannes Farhenkrugの回答hereから始めましたが、うまくいきました。私の目標はアプリケーションを分離し、サードパーティのリソースをテストしないことであるため、要求ライブラリをモックする必要がありました。

次に、python のMockライブラリについてさらに調べたところ、「Test Double」または「Fake」と呼ばれる MockResponse クラスを python Mock クラスに置き換えることができることに気付きました。

そうする利点はassert_called_withcall_argsなどへのアクセスです。追加のライブラリは必要ありません。「読みやすさ」や「よりPythonic」などの追加の利点は主観的なものであるため、それらが役割を果たす場合とそうでない場合があります.

テストダブルの代わりにpythonのモックを使用して更新された私のバージョンは次のとおりです。

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.it リンク:

https://repl.it/@abkonsta/Using-unittestMock-for-requestspost#main.py

https://repl.it/@abkonsta/Using-test-double-for-requestspost#main.py

于 2020-11-24T17:07:53.327 に答える
5

偽の応答をモックしたい場合は、次のように基本 HttpResponse クラスのインスタンスを単純にインスタンス化することもできます。

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()
于 2016-03-31T18:37:51.350 に答える
2

代わりにrequests-mockを使用できますか?

myview 関数が代わりにrequests.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

requests_mockPytest 以外のフレームワークでも使用できます。ドキュメントは優れています。

于 2022-01-10T16:16:48.537 に答える
0

pytestに追加のライブラリをインストールしたくない人のために、例あります。上記の例に基づいて、いくつかの拡張機能を付けてここに複製します。

import datetime

import requests


class MockResponse:
    def __init__(self, json_data, status_code):
        self.json_data = json_data
        self.status_code = status_code
        self.elapsed = datetime.timedelta(seconds=1)

    # mock json() method always returns a specific testing dictionary
    def json(self):
        return self.json_data


def test_get_json(monkeypatch):
    # Any arguments may be passed and mock_get() will always return our
    # mocked object, which only has the .json() method.
    def mock_get(*args, **kwargs):
        return MockResponse({'mock_key': 'mock_value'}, 418)

    # apply the monkeypatch for requests.get to mock_get
    monkeypatch.setattr(requests, 'get', mock_get)

    # app.get_json, which contains requests.get, uses the monkeypatch
    response = requests.get('https://fakeurl')
    response_json = response.json()

    assert response_json['mock_key'] == 'mock_value'
    assert response.status_code == 418
    assert response.elapsed.total_seconds() == 1


============================= test session starts ==============================
collecting ... collected 1 item

test_so.py::test_get_json PASSED                                          [100%]

============================== 1 passed in 0.07s ===============================
于 2022-02-10T21:29:44.033 に答える
0

まだ苦労している人、urllib または urllib2/urllib3 からリクエストに変換し、レスポンスをモックしようとしている人へのヒントです。モックを実装するときに、少し混乱するエラーが発生しました。

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

属性エラー: __enter__

もちろん、私がどのように機能するかについて何か知っていればwith(私は知りませんでした)、それが痕跡的で不必要なコンテキストであることがわかります( PEP 343から)。フードの下で基本的に同じことを行うため、 requests ライブラリを使用する場合は不要です。を取り外してwith裸で使用するだけでrequests.get(...)Bob's your uncle .

于 2018-12-27T22:31:36.883 に答える