I am trying to test my functions on my django api that perform external requests to external api. How can
i test the following scenarios: success, failed, and exceptions like timeout
The following is a simplified functionality
def get_quote(*args):
# log request
try:
response = requests.post(url, json=data)
# parse this response
except:
# log file :)
finally:
# log_response(...)
return parsed_response or None
None: response can be success, failed, can timeout. I want to test those kind of scenarios
You can mock the result of calling the external API and set an expected return value in the test function:
from unittest.mock import patch
from django.test import TestCase
class ExternalAPITests(TestCase):
#patch("requests.post")
def test_get_quote(self, mock):
mock.return_value = "predetermined external result"
self.assertEquals("expected return value", get_quote())
You can use the responses package - https://pypi.org/project/responses/
import unittest
import responses
from your_package import get_quote
class TestPackage(unittest.TestCase):
#responses.activate
def test_get_quote(self):
url = "http://some_fake_url.com"
responses.add(responses.POST, url, json={"test": "ok"}, status=200)
self.assertDictEqual({"test": "ok"}, get_quote(url))
#responses.activate
def test_get_quote_with_exception(self):
url = "http://some_fake_url.com"
responses.add(responses.POST, url, body=Exception('...'))
with self.assertRaises(Exception):
get_quote(url)
Related
I'm trying to work with a third party API and I am having problems with sending the request when using the requests or even urllib.request.
Somehow when I use http.client I am successful sending and receiving the response I need.
To make life easier for me, I created an API class below:
class API:
def get_response_data(self, response: http.client.HTTPResponse) -> dict:
"""Get the response data."""
response_body = response.read()
response_data = json.loads(response_body.decode("utf-8"))
return response_data
The way I use it is like this:
api = API()
rest_api_host = "api.app.com"
connection = http.client.HTTPSConnection(rest_api_host)
token = "my_third_party_token"
data = {
"token":token
}
payload = json.loads(data)
headers = {
# some headers
}
connection.request("POST", "/some/endpoint/", payload, headers)
response = connection.getresponse()
response_data = api.get_response_data(response) # I get a dictionary response
This workflow works for me. Now I just want to write a test for the get_response_data method.
How do I instantiate a http.client.HTTPResponse with the desired output to be tested?
For example:
from . import API
from unittest import TestCase
class APITestCase(TestCase):
"""API test case."""
def setUp(self) -> None:
super().setUp()
api = API()
def test_get_response_data_returns_expected_response_data(self) -> None:
"""get_response_data() method returns expected response data in http.client.HTTPResponse"""
expected_response_data = {"token": "a_secret_token"}
# I want to do something like this
response = http.client.HTTPResponse(expected_response_data)
self.assertEqual(api.get_response_data(response), expected_response_data)
How can I do this?
From the http.client docs it says:
class http.client.HTTPResponse(sock, debuglevel=0, method=None, url=None)
Class whose instances are returned upon successful connection. Not instantiated directly by user.
I tried looking at socket for the sock argument in the instantiation but honestly, I don't understand it.
I tried reading the docs in
https://docs.python.org/3/library/http.client.html#http.client.HTTPResponse
https://docs.python.org/3/library/socket.html
Searched the internet on "how to test http.client.HTTPResponse" but I haven't found the answer I was looking for.
I am trying to mock the response of api call with pytest in using monkeypatch but without success.
In a file functions.py, I have a function call an external API to get data in json format and I want to test this function
def api_call(url, token):
try:
headers = {"Authorization": "Bearer %s" % token['accessToken']}
session = requests.Session()
response = session.get(url, headers=headers)
json_data = response.json()
return json_data
except Exception as err:
print(f'Other error occurred: {err}')
My test function in file test_functions.py :
from lib import requests
import functions as extfunction
class MockResponse:
def __init__(self, json_data):
self.json_data = json_data
def json(self):
return self.json_data
def test_api_call_get(monkeypatch):
fake_token = {'accessToken' : 'djksjdskjdsjdsljdsmqqqq'}
def mock_get(*args, **kwargs):
return MockResponse({'results': 'test', 'total_sum' : 2000})
monkeypatch.setattr(requests, 'get', mock_get)
# extfunction.api_call, which contains requests.get, uses the monkeypatch
fake_url = 'https://api-test/v2'
response = extfunction.api_call(fake_url, fake_token)
assert response['results'] == 'test'
assert response['total_sum'] == 2000
During test execution, my function api_call (using requests with Get Method) is not mocked and I have an error because function is really called with fake parameters (such as fake_token)
How can I do to fake my response ?
Thanks
You should use the responses library, which is made for this purpose.
A code snippet would look like:
import functions as extfunction
import responses
#responses.activate
def test_api_call_get():
fake_token = {'accessToken' : 'djksjdskjdsjdsljdsmqqqq'}
responses.add(responses.GET, 'https://api-test/v2',
json={'results': 'test', 'total_sum' : 2000})
# extfunction.api_call, which contains requests.get, uses the monkeypatch
fake_url = 'https://api-test/v2'
response = extfunction.api_call(fake_url, fake_token)
assert response['results'] == 'test'
assert response['total_sum'] == 2000
The monkeypatch is modifying the requests function get(), as in
response = requests.get(url, headers=headers)
When you are using requests.Session().get()
This can be monkeypatched by modifying the Session class instead of the requests module.
monkeypatch.setattr(requests.Session, 'get', mock_get)
The long story short is I am working on building a server that serves as something as a chat bot. The server uses google dialog flow. Right now I have an endpoint exposed that allows me to talk to my server, when I hit that endpoint, google auth, as well as google dialog flow gets called. I am attempting to mock the response of dialog flow while leaving the actual server to respond to the network call. As of now my test looks like this.
This is my base test file:
import unittest
import mock
class BaseTest(unittest.TestCase, object):
def __init__(self, *args, **kwargs):
super(BaseTest, self).__init__(*args, *kwargs)
def auto_patch(self, patch_target):
patcher = mock.patch(patch_target)
patched = patcher.start()
self.addCleanup(patcher.stop)
return patched
This is my test file:
import json
import uuid
from os import path
from tests.base_test import BaseTest
from agent.api_service import app
import requests_mock
import pytest
from hamcrest import assert_that, has_items, equal_to
CWD = path.dirname(path.realpath(__file__))
class TestAudio(BaseTest):
def test__interact__full_no_stt(self):
payload = json.load(open("tests/json_payloads/test__interact__full_audio.json"))
u_session_id = str(uuid.uuid1())
payload["session_id"] = u_session_id
#mock a 500 back from STT
with open("tests/json_payloads/stt_500.json", "r") as issues_file:
mock_response = issues_file.read()
with requests_mock.Mocker() as m:
m.register_uri('POST', 'https://speech.googleapis.com/v1/speech:recognize', text=mock_response)
request, response = app.test_client.post("/agent_service/interact", data=json.dumps(payload))
self.assertEqual(200, response.status)
This is my google stt file:
import json
import requests
from agent.exceptions import GoogleSTTException
from agent.integrations.google.google_auth_service import get_auth_token
from agent.integrations.google.google_stt_request import GoogleSTTRequest
from agent.integrations.google.google_stt_response import GoogleSTTResponse
def speech_to_text(audio_string):
try:
google_stt_request = GoogleSTTRequest(audio_string).to_payload()
request_headers = dict()
request_headers['Authorization'] = 'Bearer ' + get_auth_token()
request_headers['Content-Type'] = 'application/json'
url = 'https://speech.googleapis.com/v1/speech:recognize'
google_response = requests.post(url, data=json.dumps(google_stt_request), headers=request_headers)
response = GoogleSTTResponse(google_response.json())
return response
except Exception as e:
raise GoogleSTTException('Received an error invoking google stt {}'.format(e))
Does anyone have any ideas on how I can mock the response from the google stt call, without touching the google auth call or the server call itself? I have tried a handful of things and so far no luck. I either end up mocking nothing, or both the google stt and auth call.
So I ended up moving away from the original implementation, but this is what got me there.
#responses.activate
def test__interact__full_no_stt(self):
payload = json.load(open("tests/json_payloads/test__interact__full_audio.json"))
u_session_id = str(uuid.uuid1())
payload["session_id"] = u_session_id
#mock a 500 back from STT
responses.add(responses.POST,
'https://speech.googleapis.com/v1/speech:recognize',
json={'error': 'broken'}, status=500)
request, response = app.test_client.post("/agent_service/interact", data=json.dumps(payload))
self.assertEqual(200, response.status)
result = response.json
Responses makes this much easier, just be sure to include the annotation at the top of the test.
I have a function that needs to utilize a fixture in my test suite. This is just a small helper function that helps to generate a full URL.
def gen_url(endpoint):
return "{}/{}".format(api_url, endpoint)
I have a fixture in conftest.py that returns the URL:
#pytest.fixture(params=["http://www.example.com"])
def api_url(request):
return request.param
#pytest.fixture(params=["MySecretKey"])
def api_key(request):
return request.param
Finally, in my test function, I need to call my gen_url:
def test_call_action_url(key_key):
url = gen_url("player")
# url should equal: 'http://www.example.com/player'
# Do rest of test here...
When I do this, though, it throws an error saying that api_url isn't defined when gen_url is called. If I add api_url as a second parameter, I need to pass it as a second parameter. That's...not what I want to do.
Can I add api_url as a second parameter to gen_url without being required to pass it from the tests? Why can't I use it like api_key in my test_* function?
If you make gen_url a fixture, it can request api_url without explicitly passing it:
#pytest.fixture
def gen_url(api_url):
def _gen_url(endpoint):
return '{}/{}'.format(api_url, endpoint)
return _gen_url
def test_call_action_url(api_key, gen_url):
url = gen_url('player')
# ...
Additionally, if api_key is only used to make requests, a TestClient class
could encapsulate it, so test methods would only require the client:
try:
from urllib.parse import urljoin # Python 3
except ImportError:
from urlparse import urljoin # Python 2
import requests
#pytest.fixture
def client(api_url, api_key):
class TestClient(requests.Session):
def request(self, method, url, *args, **kwargs):
url = urljoin(api_url, api_key)
return super(TestClient, self).request(method, url, *args, **kwargs)
# Presuming API key is passed as Authorization header
return TestClient(headers={'Authorization': api_key})
def test_call_action_url(client):
response = client.get('player') # requests <api_url>/player
# ...
multiple problems with your code, the fixture is not visible in your test code untill and unless you use it as your test parameter, you are not passing both the fixtures (api_url and api_key) to your test function and subsequently to your helper function.
Here is the modified code (untested)
def gen_url(api_url, endpoint):
return "{}/{}".format(api_url, endpoint)
def test_call_action_url(api_url, api_key):
url = gen_url(api_url, "player")
# url should equal: 'http://www.example.com/player'
# Do rest of test here with api_key here...
I have an endpoint like this:-
#app.route('/name', methods=['POST'])
#limiter.limit("2000/day;300/hour;5/minute", key_func = get_uid_from_request)
#authenticate
def post(user):
How do I make a fake post request using unittest module?
Well, you can make an actual post request
import requests
def test_post():
resp = requests.post('http://localhost/name',
data={'arg': 'value'},
cookies={'from-my': 'browser'})
assert resp.status_code == 200
I would recommend using py.test instead of unittest, but if you must use unittest
class TestPost(unittest.TestCase):
def test_post(self):
resp = requests.post('http://localhost/name')
self.assertEqual(resp.status_code, 200)
You could do like already suggested, and do an acceptance test against a running test instance with real post requests.
You can also check out Flasks documentation for testing at http://flask.pocoo.org/docs/0.10/testing/ which demonstrates how to do unit tests in which you can mock the incoming requests and test results.