Come posso deridere le richieste e la risposta?


221

Sto cercando di usare il pacchetto simulato Pythons per deridere il requestsmodulo Pythons . Quali sono le chiamate di base per farmi lavorare nello scenario seguente?

Nel mio views.py, ho una funzione che fa ogni volta una varietà di chiamate request.get () con risposta diversa

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

Nella mia classe di test voglio fare qualcosa del genere, ma non riesco a capire le esatte chiamate al metodo

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

Passo 2:

Chiama il mio punto di vista

Passaggio 3:

verifica che la risposta contenga "a response", "b response", "c response"

Come posso completare il passaggio 1 (deridendo il modulo delle richieste)?


Risposte:


277

Ecco come puoi farlo (puoi eseguire questo file così com'è):

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

Nota importante: se la tua MyGreatClassclasse vive in un pacchetto diverso, ad esempio my.great.package, devi prendere in giro my.great.package.requests.getinvece di "request.get". In tal caso il tuo caso di test sarebbe simile al seguente:

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

Godere!


2
La classe MockResponse è un'ottima idea! Stavo cercando di falsificare un resuests. Oggetto della classe di risposta ma non è stato facile. Potrei usare questo MockResponse al posto della cosa reale. Grazie!
Yoshi,

@yoshi Sì, mi ci è voluto un po 'per avvolgere la testa in giro per i finti in Python, ma per me funziona abbastanza bene!
Johannes Fahrenkrug,

10
E in Python 2.x, basta sostituire from unittest import mockcon import mocke il resto funziona così com'è. È necessario installare il mockpacchetto separatamente.
Haridsv,

3
Fantastico. Ho dovuto apportare una leggera modifica in Python 3 in base mock_requests_getalle necessità yieldanziché a returncausa della modifica
apportata

1
era quello che la domanda originariamente stava ponendo. Ho escogitato dei modi (comprimere l'app nel pacchetto e montare un test_client () per eseguire la chiamata). grazie per il post però, stava ancora usando la spina dorsale del codice.
Suicide Bunny,

141

Prova a utilizzare la libreria di risposte :

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

offre una bella comodità sull'impostare tutto il deridere da soli

C'è anche HTTPretty :

Non è specifico per la requestsbiblioteca, più potente per certi versi anche se ho scoperto che non si presta molto bene all'ispezione delle richieste intercettate, cosa che responsesfa abbastanza facilmente

C'è anche httmock .


A prima vista, non ho visto un modo per responsesabbinare un URL jolly - ovvero, implementare la logica di callback come "prendi l'ultima parte dell'URL, cercala in una mappa e restituisci il valore corrispondente". È possibile e mi manca solo?
scubbo,

1
@scubbo puoi passare una regex precompilata come parametro url e usare lo stile di callback github.com/getsentry/responses#dynamic-responses questo ti darà il comportamento jolly che desideri penso (può accedere all'URL passato requestsull'arg ricevuto dalla funzione di richiamata)
Anentropico

48

Ecco cosa ha funzionato per 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)))

3
Funzionerà se ti aspetti risposte di testo / html. Se stai deridendo un'API REST, vuoi controllare il codice di stato, ecc., Allora la risposta di Johannes [ stackoverflow.com/a/28507806/3559967] è probabilmente la strada da percorrere.
Antony,

5
Per Python 3, utilizzare from unittest import mock. docs.python.org/3/library/unittest.mock.html
phoenix

32

Ho usato richieste-mock per scrivere test per moduli separati:

# module.py
import requests

class A():

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

E i test:

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

Dove trovi m in '(sé, m):'
Denis Evseev il

16

questo è il modo in cui deridi richieste.post, modificalo nel tuo metodo 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
Cosa succede se desidero deridere una funzione? Come deridere questo per esempio: mockresponse.json () = {"chiave": "valore"}
primoz

1
@primoz, ho usato una funzione anonima / lambda per questo:mockresponse.json = lambda: {'key': 'value'}
Tayler

1
Oppuremockresponse.json.return_value = {"key": "value"}
Lars Blumberg,

5

Se vuoi deridere una risposta falsa, un altro modo per farlo è semplicemente istanziare un'istanza della classe HttpResponse di base, in questo modo:

from django.http.response import HttpResponseBase

self.fake_response = HttpResponseBase()

Questa è la risposta a quello che stavo cercando di trovare: ottenere un falso oggetto di risposta django in grado di superare la gamma di middleware per un test quasi e2e. HttpResponse, piuttosto che ... Base, ha fatto il trucco per me. Grazie!
low_ghost

4

Un modo possibile per aggirare le richieste è usare la libreria betamax, registra tutte le richieste e dopo che se fai una richiesta nello stesso url con gli stessi parametri il betamax utilizzerà la richiesta registrata, la sto usando per testare il web crawler e mi fa risparmiare molto tempo.

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/


Si noti che betamax è progettato per funzionare solo con le richieste , se è necessario acquisire richieste HTTP rese l'API HTTP di livello inferiore dell'utente come httplib3 o con aiohttp alternativo o librerie client come boto ... utilizzare invece vcrpy che funziona a livello inferiore. Altro su github.com/betamaxpy/betamax/issues/125
Le Hibou

0

Solo un suggerimento utile per coloro che stanno ancora lottando, convertendosi da urllib o urllib2 / urllib3 in richieste E cercando di deridere una risposta- Stavo ottenendo un errore leggermente confuso durante l'implementazione del mio mock:

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

AttributeError: __enter__

Bene, ovviamente, se sapessi qualcosa su come withfunziona (non l'ho fatto), saprei che era un contesto rudimentale e non necessario (dal PEP 343 ). Inutili quando si utilizza la libreria richieste perché fa praticamente la stessa cosa per voi sotto il cofano . Basta rimuovere il withe usare nudo requests.get(...)e Bob è tuo zio .


0

Aggiungerò queste informazioni poiché ho avuto difficoltà a capire come deridere una chiamata API asincrona.

Ecco cosa ho fatto per deridere una chiamata asincrona.

Ecco la funzione che volevo testare

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

Hai ancora bisogno della classe 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

Si aggiunge la classe MockResponseAsync

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

    async def getResponse(self):
        return self.response

Ecco il test. La cosa importante qui è creare la risposta prima poiché la funzione init non può essere asincrona e la chiamata a getResponse è asincrona, quindi tutto è stato verificato.

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

Se hai un modo migliore di farlo, dimmelo ma penso che sia abbastanza pulito in quel modo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.