リクエストとレスポンスをモックするにはどうすればよいですか?


221

Pythonsモックパッケージを使用してPythons requestsモジュールをモックしようとしています。以下のシナリオで私を働かせるための基本的なコールは何ですか?

私の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:

応答に「応答」、「b応答」、「c応答」が含まれていることを確認します

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


回答:


277

これはあなたがそれを行う方法です(あなたはこのファイルをそのまま実行することができます):

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

楽しい!


2
MockResponseクラスは素晴らしいアイデアです!resuests.Responseクラスオブジェクトを偽装しようとしていましたが、簡単ではありませんでした。このMockResponseを本物の代わりに使用できます。ありがとうございました!
yoshi

@yoshiええ、Pythonでモックに頭をまわすのに少し時間がかかりましたが、これは私にとってはかなりうまくいきます!
Johannes Fahrenkrug 2015年

10
そして、Python 2.xでは、単に置き換えるfrom unittest import mockimport mock、残りがあるとして動作します。mockパッケージは個別にインストールする必要があります。
haridsv

3
素晴らしい。私はとしてPython 3のわずかな変化をしなければならなかったmock_requests_getに必要なyieldの代わりに、returnPythonで3イテレータを返すにあるため変化
eripを

1
それが質問がもともと尋ねていたものでした。私は方法を見つけました(アプリをパッケージにパックし、test_client()を呼び出して呼び出しを実行します)。ただし、投稿のおかげで、コードのバックボーンをまだ使用していました。
Suicide Bunny

141

応答ライブラリを使用しみてください:

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

すべてのモックを自分で設定するよりもかなり便利です

HTTPrettyもあります

それはrequestsライブラリに固有ではなく、いくつかの点でより強力ですが、インターセプトしたリクエストを検査するのにはあまり適していresponsesません。

httmockあります。


一見、responsesワイルドカードURLを照合する方法がわかりませんでした。つまり、「URLの最後の部分を取得し、マップで検索して、対応する値を返す」のようなコールバックロジックを実装します。それは可能ですか、私はそれを逃していますか?
scubbo

1
@scubboプリコンパイルされた正規表現をurlパラメータとして渡し、コールバックスタイルgithub.com/getsentry/responses#dynamic-responsesを使用できます。これにより、必要なワイルドカード動作が得られます(request引数で渡されたURLにアクセスできます)コールバックfuncによって受信されました)
エントロピー

48

これが私のために働いたものです:

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
これは、text / html応答を期待している場合に機能します。REST APIをモックしている場合、ステータスコードなどを確認したい場合は、Johannesからの回答[ stackoverflow.com/a/28507806/3559967]適切です。
アントニー

5
Python 3の場合は、を使用しますfrom unittest import mockdocs.python.org/3/library/unittest.mock.html
フェニックス

32

別のモジュールのテストを作成するために、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()

'(self、m):'でmはどこにありますか
Denis Evseev

16

これは、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

1
関数をモックしたい場合はどうなりますか?これをモックする方法の例:mockresponse.json()= {"key": "value"}
primoz

1
@primoz、そのために無名関数/ラムダを使用しました:mockresponse.json = lambda: {'key': 'value'}
Tayler

1
またはmockresponse.json.return_value = {"key": "value"}
Lars Blumberg 2017

5

偽の応答をモックしたい場合は、次のように、ベースのHttpResponseクラスのインスタンスを単純にインスタンス化する方法もあります。

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

これが、私が見つけようとしていた答えです。ほとんどe2eテスト用のミドルウェアの全範囲を通過できる偽のdjango応答オブジェクトを取得します。HttpResponse... Baseではなく、私のためにトリックを行いました。ありがとう!
low_ghost

4

リクエストを回避する1つの可能な方法は、ライブラリ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/


ことを注意ベータマックスは唯一で動作するように設計されたリクエストは、HTTPのような作られたユーザー低レベルのHTTP API要求キャプチャする必要がある場合は、httplib3、または代替とaiohttpのような、またはクライアントのlibs のboto ...使用vcrpy低いレベルで働く代わりに。詳細はgithub.com/betamaxpy/betamax/issues/125
Le Hibou

0

まだ苦労していて、urllibまたはurllib2 / urllib3からリクエストに変換し、応答をモックしようとしている人への有用なヒント-私のモックを実装すると、少し混乱するエラーが発生しました:

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

AttributeError:__enter__

もちろん、どのようにwith機能するかを知っていれば(私は知りません)、それが(PEP 343からの)痕跡的で不必要なコンテキストだったと思います。不要な、それは基本的にあなたのために同じことを行うための要求ライブラリを使用する場合にボンネットの下。を削除して、ベアとボブの叔父を使用しますwithrequests.get(...)


0

非同期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)

あなたがそれを行うより良い方法を持っているなら教えてくださいが、それはそのようにかなりきれいだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.