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)
Related
there is a fixture in the conftest.py file:
#pytest.fixture()
def get_request(url=base_url, headers=None, params=None):
if headers is None:
headers = {}
elif params is None:
params = {}
session = requests.Session()
response = session.get(url=url,
headers=get_token(),
params=params,
verify=False,
)
return response
which is used by the test function in the test.py file:
import pytest
import requests
def test_get_request2(get_request):
response = get_request(test_url + '/endpoint')
when calling the function, an error occurs:
test_.py:17: in test_get_request2
return get_request(test_url + '/endpoint')
E TypeError: 'Response' object is not callable
What is the problem?
I expect that the result of the execution of the fixture will be pulled into the test function (response to receive the request).
What you expect is exactly what is happening. The fixture is executed and the result (a Response object) is passed to the test as an argument.
The problem is in your test:
def test_get_request2(get_request):
response = get_request(test_url + '/endpoint')
Here get_request is a response object, returned by the fixture and not a function that you can call.
Now I'm not completely sure what your intentions were, but if you are trying to verify that the response is successful you can do that by:
def test_get_request2(get_request):
assert get_request.status_code == 200
Hope that makes it a bit more clear what is happening.
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 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'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
For testing purposes I'm trying to create a Response() object in python but it proves harder then it sounds.
i tried this:
from requests.models import Response
the_response = Response()
the_response.code = "expired"
the_response.error_type = "expired"
the_response.status_code = 400
but when I attempted the_response.json() i got an error because the function tries to get len(self.content) and a.content is null.
So I set a._content = "{}" but then I get an encoding error, so I have to change a.encoding, but then it fails to decode the content....
this goes on and on. Is there a simple way to create a Response object that's functional and has an arbitrary status_code and content?
That because the _content attribute on the Response objects (on python3) has to be bytes and not unicodes.
Here is how to do it:
from requests.models import Response
the_response = Response()
the_response.code = "expired"
the_response.error_type = "expired"
the_response.status_code = 400
the_response._content = b'{ "key" : "a" }'
print(the_response.json())
Create a mock object, rather than trying to build a real one:
from unittest.mock import Mock
from requests.models import Response
the_response = Mock(spec=Response)
the_response.json.return_value = {}
the_response.status_code = 400
Providing a spec ensures that the mock will complain if you try to access methods and attributes a real Response doesn't have.
Just use the responses library to do it for you:
import responses
#responses.activate
def test_my_api():
responses.add(responses.GET, 'http://whatever.org',
json={}, status=400)
...
This has the advantage that it intercepts a real request, rather than having to inject a response somewhere.
Another approach by using the requests_mock library, here with the provided fixture:
import requests
def test_response(requests_mock):
requests_mock.register_uri('POST', 'http://test.com/', text='data', headers={
'X-Something': '1',
})
response = requests.request('POST', 'http://test.com/', data='helloworld')
...