I'm testing for a specific response code and want to mock out a test case when the code is something different, like unauthorized 401. I'm using the Python 3.7 http.client library and pytest
So far I tried to use the #patch decorator and call a function with side_effect to trigger the exception
my test case:
from unittest import mock
from application import shorten_url
def mock_status(url):
raise ConnectionError
#patch("application.shorten_url", side_effect=mock_status)
def test_bitly(client):
with pytest.raises(ConnectionError) as e:
shorten_url("something")
my code:
def shorten_url(url):
conn = http.client.HTTPSConnection("api-ssl.bitly.com", timeout=2)
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer abcd",
}
payload = json.dumps({"long_url": url})
conn.request("POST", "/v4/shorten", payload, headers)
res = conn.getresponse()
if not res.status == 201:
raise ConnectionError
data = json.loads(res.read())
return data["link"]
I don't really understand how to raise this exception correctly using mock and side_effect.
A friend helped me with the issue, this seems to work (it's still very confusing for me):
from unittest.mock import patch, MagicMock
#patch("http.client.HTTPSConnection")
#patch("http.client.HTTPResponse")
def test_bitly(mock_conn, mock_res):
mock_res.status = 400
mock_conn.getresponse = MagicMock(return_value=mock_res)
with pytest.raises(ConnectionError):
shorten_url("fake-url")
I think this answer is easier to understand. First I created a fake http connection and response:
class FakeHTTPConnection:
def __init__(self, status):
self.status = status
def request(self, *args):
# If you need to do any logic to change what is returned, you can do it in this class
pass
def getresponse(self):
return FakeHTTPResponse(self.status)
class FakeHTTPResponse:
def __init__(self, status):
self.status = status
Then in my test class, I overrode http.client.HTTPConnection to create my instance instead.
class TestFoo(unittest.TestCase):
#patch('http.client.HTTPConnection', new=MagicMock(return_value=FakeHTTPConnection(200)))
def test_foo_success(self):
conn = http.client.HTTPConnection("127.0.0.1")
conn.request("GET", "/endpoint")
response = conn.getresponse()
success = response.status == http.HTTPStatus.OK
Related
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 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)
I have a python function that calls out to an API using the request function. I want to test a 200 path, and then test a 500 error. I can't seem to figure out how to do it when looking at the requests-mock documentation.
Here is what I want to test.
def get_sku_data(sku: str):
"""
Retrieve a single product from api
"""
uri = app.config["SKU_URI"]
url = f"{uri}/{sku}"
headers = {
"content-type": "application/json",
"cache-control": "no-cache",
"accept": "application/json",
}
try:
retry_times = 3
response = retry_session(retries=retry_times).get(url, headers=headers)
return response.json()
except ConnectionError as ex:
raise Exception(f"Could not connect to sku api at {uri}. {ex}.")
except requests.exceptions.RetryError:
raise Exception(f"Attempted to connect to {uri} {retry_times} times.")
def retry_session(
retries, backoff_factor=0.3, status_forcelist=(500, 502, 503, 504)
) -> requests.Session:
"""
Performs a retry
"""
session = requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
Here is my test stub that I'm trying to get going
# These test use pytest https://pytest.readthedocs.io/en/latest/
# Note: client is a pytest fixture that is dependency injected from src/tests/conftest.py
import json
import pytest
from src.app import create_app
import requests
import requests_mock
#pytest.fixture
def app():
app = create_app()
return app
def test():
session = requests.Session()
adapter = requests_mock.Adapter()
session.mount("mock", adapter)
adapter.register_uri("GET", "mock://12", text="data")
resp = session.get("mock://12")
assert resp.status_code == 200
assert resp.text == "data"
I'm a bit new to python testing, so any help would be very much appreciated.
My perception of how it worked was wrong.
I register the URI I'm testing against and instead of calling session.get, I actually call the function under test.
I have a class called Client which uses the requests module to interact with a service. It has methods like:
def get_resource(self, url, headers):
response = requests.get(url, headers, auth=self.auth)
return response
Now I want to call some methods before and after each call to the requests module. Something like:
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
I'm having trouble finding a way to do this without having to rewrite all Client methods. The most straightforward way is to change the calls to the request module to calls to self and add the calls to the methods there.
def get_resource(self, url, headers):
response = self.__get(url, headers, auth=self.auth)
return response
def __get(self, headers, auth):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
But this requires me to change all the call sites and duplicate functionality of the request module. I've tried to use decorators to add these method calls to the request module functions, but got stuck with how to pass in self to the decorator.
I'm sure there's an elegant way to do this in Python.
You can use monkey patching.
read this : Python: Monkeypatching a method of an object
import requests
def get(self, url, params=None, **kwargs):
self.add_request_header(self.headers)
response = requests.get(url, self.headers, auth=self.auth)
self.process_response_headers()
setattr(requests.Session, 'get', requests.Session.get)
s = requests.Session()
I think in this case decorators will not be as good as it sounds and OOP is a better approach to your problem. You could use a base class Client:
class Client(object):
def __init__(self, auth):
self.auth = auth
def add_request_header(self, headers):
pass
def process_response_headers(self):
pass
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
And create another subclasses with other implementations of add_request_header and/or process_response_headers so later you just need to instantiate the class that better suites your case