GitHubじゃ!Pythonじゃ!

GitHubからPython関係の優良リポジトリを探したかったのじゃー、でも英語は出来ないから日本語で読むのじゃー、英語社会世知辛いのじゃー

getsentry

responses – Python Requestsライブラリを模擬するためのユーティリティ

投稿日:

Python Requestsライブラリを模擬するためのユーティリティ。

反応

Pythonライブラリのリクエストを模擬するためのユーティリティライブラリ。

注意

レスポンスにはPython 2.7以降が必要で、> 2.0以上のリクエスト

インストール

pip install responses

基本

responsesのコアは模擬応答の登録から来ます:

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

一致しなかったURLを取得しようとすると、 responsesConnectionErrorを送出しConnectionError

import responses
import requests

from requests.exceptions import ConnectionError

@responses.activate
def test_simple():
    with pytest.raises(ConnectionError):
        requests.get('http://twitter.com/api/1/foobar')

最後に、 Exceptionを本文として渡してリクエストのエラーをトリガーすることができます。

import responses
import requests

@responses.activate
def test_simple():
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
                  body=Exception('...'))
    with pytest.raises(Exception):
        requests.get('http://twitter.com/api/1/foobar')

応答パラメータ

レスポンスはadd paramsを介して自動的に登録されますが、直接渡すこともできます:

import responses

responses.add(
    responses.Response(
        method='GET',
        url='http://example.com',
    ),
)

次の属性は、応答モックに渡すことができます。

メソッド( str
HTTPメソッド(GET、POSTなど)。
url( strまたはコンパイルされた正規表現)
完全なリソースのURL。
match_querystring( bool
デフォルトでは無効です。 要求を一致させる場合は、クエリ文字列を含めます。
body( strまたはBufferedReader
レスポンスボディ。
ジョソン
JSON応答本体を表すPythonオブジェクト。 適切なContent-Typeを自動的に設定します。
status( int
HTTPステータスコード。
content_type( content_type
デフォルトはtext/plainです。
ヘッダー( dict
応答ヘッダー。
ストリーム( bool
デフォルトでは無効です。 応答でストリーミングAPIを使用する必要があることを示します。

動的応答

コールバックを利用して動的な応答を提供することができます。 コールバックは、( statusheadersbody )のタプルを返す必要があります。

import json

import responses
import requests

@responses.activate
def test_calc_api():

    def request_callback(request):
        payload = json.loads(request.body)
        resp_body = {'value': sum(payload['numbers'])}
        headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
        return (200, headers, json.dumps(resp_body))

    responses.add_callback(
        responses.POST, 'http://calc.com/sum',
        callback=request_callback,
        content_type='application/json',
    )

    resp = requests.post(
        'http://calc.com/sum',
        json.dumps({'numbers': [1, 2, 3]}),
        headers={'content-type': 'application/json'},
    )

    assert resp.json() == {'value': 6}

    assert len(responses.calls) == 1
    assert responses.calls[0].request.url == 'http://calc.com/sum'
    assert responses.calls[0].response.text == '{"value": 6}'
    assert (
        responses.calls[0].response.headers['request-id'] ==
        '728d329e-0e86-11e4-a748-0c84dc037c13'
    )

コンテキストマネージャとしての応答

import responses
import requests

def test_my_api():
    with responses.RequestsMock() as rsps:
        rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
                 body='{}', status=200,
                 content_type='application/json')
        resp = requests.get('http://twitter.com/api/1/foobar')

        assert resp.status_code == 200

    # outside the context manager requests will hit the remote server
    resp = requests.get('http://twitter.com/api/1/foobar')
    resp.status_code == 404

宣言された応答に関するアサーション

コンテキストマネージャとして使用する場合、URLは登録されているがアクセスされていない場合、レスポンスはデフォルトでアサーションエラーを発生させます。 これは、 assert_all_requests_are_fired値を渡すことで無効にすることができます:

import responses
import requests

def test_my_api():
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
        rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
                 body='{}', status=200,
                 content_type='application/json')

複数の回答

同じURLに対して複数の応答を追加することもできます。

import responses
import requests

@responses.activate
def test_my_api():
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
                  body='{}', status=200,
                  content_type='application/json')

    resp = requests.get('http://twitter.com/api/1/foobar')
    assert resp.status_code == 500
    resp = requests.get('http://twitter.com/api/1/foobar')
    assert resp.status_code == 200

コールバックを使用してレスポンスを変更する

サブクラス化/ mixinsによるリクエストでカスタマイズされた処理を使用する場合、または低レベルでリクエストとやりとりするライブラリツールがある場合は、テストの環境を完全にシミュレートするために拡張レスポンスオブジェクトを拡張レスポンスオブジェクトに追加する必要があります。 response_callbackを使用することができます。これは、呼び出し側に返される前にライブラリによってラップされます。 コールバックは、単一の引数として応答を受け取り、単一の応答オブジェクトを返すことが期待されます。

import responses
import requests

def response_callback(resp):
    resp.callback_processed = True
    return resp

with responses.RequestsMock(response_callback=response_callback) as m:
    m.add(responses.GET, 'http://example.com', body=b'test')
    resp = requests.get('http://example.com')
    assert resp.text == "test"
    assert hasattr(resp, 'callback_processed')
    assert resp.callback_processed is True

実際のリクエストを通す

場合によっては、特定のリクエストがレスポンスを通過し、実際のサーバーにヒットすることを許可したい場合があります。 これは ‘パススルー’の方法で行うことができます:

import responses

@responses.activate
def test_my_api():
    responses.add_passthru('https://percy.io')

これにより、その接頭辞に一致する要求(モック応答として登録されていない)が、標準的な動作を使用してパススルーに許可されます。







-getsentry

執筆者: