Mocking requests.post and requests.json decoder python - python

I'm creating a test suite for my module that uses the requests library quite a bit. However, I'm trying to mock several different return values for a specific request, and I'm having trouble doing so. Here is my code snippet that doesn't work:
class MyTests(unittest.TestCase):
#patch('mypackage.mymodule.requests.post')
def test_change_nested_dict_function(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json = nested_dictionary
modified_dict = mymodule.change_nested_dict()
self.assertEqual(modified_dict['key1']['key2'][0]['key3'], 'replaced_value')
The function I am attempting to mock:
import requests
def change_nested_dict():
uri = 'http://this_is_the_endpoint/I/am/hitting'
payload = {'param1': 'foo', 'param2': 'bar'}
r = requests.post(uri, params=payload)
# This function checks to make sure the response is giving the
# correct status code, hence why I need to mock the status code above
raise_error_if_bad_status_code(r)
dict_to_be_changed = r.json()
def _internal_fxn_to_change_nested_value(dict):
''' This goes through the dict and finds the correct key to change the value.
This is the actual function I am trying to test above'''
return changed_dict
modified_dict = _internal_fxn_to_change_nested_value(dict_to_be_changed)
return modified_dict
I know a simple way of doing this would be to not have a nested function, but I am only showing you part of the entire function's code. Trust me, the nested function is necessary and I really do not want to change that part of it.
My issue is, I don't understand how to mock requests.post and then set the return value for both the status code and the internal json decoder. I also can't seem to find a way around this issue since I can't seem to patch the internal function either, which also would solve this problem. Does anyone have any suggestions/ideas? Thanks a bunch.

I bumped here and although I agree that possibly using special purpose libraries is a better solution, I ended up doing the following
from mock import patch, Mock
#patch('requests.post')
def test_something_awesome(mocked_post):
mocked_post.return_value = Mock(status_code=201, json=lambda : {"data": {"id": "test"}})
This worked for me for both getting the status_code and the json() at the receiver end while doing the unit-test.
Wrote it here thinking that someone may find it helpful.

When you mock a class each child method is set up as a new MagicMock that in turn needs to be configured. So in this case you need to set the return_value for mock_post to bring the child attribute into being, and one to actually return something, i.e:
mock_post.return_value.status_code.return_value = 200
mock_post.return_value.json.return_value = nested_dictionary
You can see this by looking at the type of everything:
print(type(mock_post))
print(type(mock_post.json))
In both cases the type is <class 'unittest.mock.MagicMock'>

Probably it is better for you to look at some specialized libraries for requests testing:
responses
requests-mock
requests-testing
They provide clean way to mock responses in unittests.

An alternate approach is to just create an actual Response object and then do a configure_mock() on the original mock.
from requests import Response
class MyTests(unittest.TestCase):
#patch('mypackage.mymodule.requests.post')
def test_change_nested_dict_function(self, mock_post):
resp = Response()
resp.status_code = 200
resp.json = nested_dictionary
mock_post.configure_mock(return_value=resp)
...

Related

caching.memoize & response_filter for internal server errors

I am using flask_caching to cache responses of my flask API. I am using the decorator on my routes like this
import random
class Status(Resource):
#cache.memoize(timeout=60) # cache for a minute
def post(self):
return random.randint(0, 5)
which will return the same random number for a minute. However, what if the random function (read: "any functionality inside the route") breaks, and the route returns a 500 internal server error? As far as I know, flask_caching would be caching this, and return the bad response for all further calls within a minute, which is not what I want.
I read into this and found the response_filter parameter, which can be added to the decorator easily, seemingly specifically to prevent this from happening ("Useful to prevent caching of code 500 responses.", from the docs:
https://flask-caching.readthedocs.io/en/latest/api.html?highlight=response_filter#flask_caching.Cache.memoize)
#cache.memoize(timeout=60, response_filter=callable_check_for_500(???))
However, I am unable to find an example of this use case. It says "If the callable returns False, the content will not be cached." - how do I implement this callable to check if the status code is 500? Any links or ideas appreciated
I figured out "a solution", but I'm not entirely happy with it
basically, the check_500() function gets the argument resp by default, however its not the full response object, and unfortunately lacks the status_code attribute, like I expected.
the status code itself is in the data, and I'm just looking at the last entry of the response, which is all the data returned. In my case its just the returned json as [0], and the status_code at [-1].
implementation is currently as follows:
#cache.memoize(timeout=60, response_filter=check_500) # cache for a minute
with the callable check_500 function defined above
def check_500(resp):
if resp[-1] == 500:
return False
else:
return True
This works pretty much like above_c_level suggested in the comment, so thank you very much, however I would advise to look at the last index of the response instead of checking if 500 is in the response data at all. It still seems a bit wonky, if anyone has a more elaborate idea, feel free to post another answer.

How to mock function to return response with attributes?

I'm messing around with unittest.mock and got some problems with it.
I have an object client with method get_messages() which returns response with attributes data and has_more. I want to mock it to return fixed data and has_more in first call and another fixed data and has_more in second call.
In first call I want to receive object response with attributes:
data=['msg1', 'msg2']
has_more=True
In second call I want to receive object response with attributes:
data=['msg3', 'msg4']
I've been trying doing it this way, but I'm kind of confused, no idea if this is the way.
#patch('Client')
def test_client_returns_correct_messages(self, MockClient):
MockWebClient.get_messages.side_effects = [
Mock(name='response',
data={'messages': received_messages,
'has_more': True}),
Mock(name='response',
data={'messages': received_messages,
'has_more': False})]
messages = client.get_messages()
Okay, I found the answer... Generally my code was fine, but I made a typo: side_effects instead of side_effect - NOTE THE s. It should be side_effect. Mock accepts everything, so it didn't rise an error. Will definitely use specs next time :D I still don't know if this is the correct way to do this, but it works.
this is the working code:
#patch('Client')
def test_client_returns_correct_messages(self, MockClient):
MockWebClient.get_messages.side_effect = [
Mock(name='response',
data={'messages': received_messages,
'has_more': True}),
Mock(name='response',
data={'messages': received_messages,
'has_more': False})]
messages = client.get_messages()
Accordig to docs
If you pass in an iterable, it is used to retrieve an iterator which must yield a value on every call. This value can either be an exception instance to be raised, or a value to be returned from the call to the mock (DEFAULT handling is identical to the function case).
Here is an example of a call, twice, from a same method but with two different answers.
import os
from unittest.mock import patch
#patch('os.path.curdir', side_effect=[True, False])
def test_side_effect(mock_curdir):
print(os.path.curdir())
print(os.path.curdir())
>>> test_side_effect()
True
False

Consolidating redundant parameters in requests

I'm using the requests module in my code (obviously to do requests), and my code is quickly getting out of hand because of redundant parameters that I need to include for each request:
def one(url, data, headers, cert):
...
return requests.post(url, json=data, headers=headers, verify=cert)
def two(otherurl, otherheaders, cert):
...
response = requests.get(otherurl, headers=otherheaders, verify=cert).json()
Is there a way to tell every request to use verify=cert without having to include it within every request statement? I'm thinking session() should be able to do this, although I have no idea how to use it. I'm just trying to minimize the repeating of things that maybe could be set globally within my script. Maybe this is not possible or how it actually works? thanks in advance.
You can use functools.partial to override these functions with verify=cert passed as an argument by default:
from functools import partial
requests.post = partial(requests.post, verify=cert)
requests.get = partial(requests.get, verify=cert)
Or if you look at the source code of requests, you'll find that both of these two functions are simply wrappers to the requests.request function, which in turn is a wrapper to the requests.Session.request method. You can therefore override requests.Session.request instead to have all the HTTP methods overridden with one statement. Since it's a method and not an unbound function, however, you have to use functools.partialmethod instead:
from functools import partialmethod
requests.Session.request = partialmethod(requests.Session.request, verify=cert)

How to get a request that is not the last in HttPretty?

Using the HTTPretty library for Python, I can create mock HTTP responses for my unit tests. When the code I am testing runs, instead of my request reaching the third party, the request is intercepted and my code receives the response I configured.
I then use last_request() and can check the url my code requested, any parameters, etc.
What I would like is to know how can I access not just the last request but also any other requests my code sent before the last one.
This seems to be possible. In the documentation it uses a list called latest_requests. For example here
But that doesn't seem to work for me. I get an AttributeError AttributeError: module 'httpretty' has no attribute 'latest_requests'
Here is some code that illustrates what I am trying to do and where I get AttributeError
import httpretty
import requests
httpretty.enable()
httpretty.register_uri(
method=httpretty.GET,
uri='http://www.firsturl.com',
status=200,
body='First Body'
)
httpretty.enable()
httpretty.register_uri(
method=httpretty.GET,
uri='http://www.secondurl.com',
status=200,
body='secondBody'
)
firstresponse = requests.get('http://www.firsturl.com')
secondresponse = requests.get('http://www.secondurl.com')
print(httpretty.latest_requests[-1].url)
# clean up
httpretty.disable()
httpretty.reset()
Thanks!!
Unfortunately, after reading the docs and attempting to get your code working, I can only describe the documentation as blatantly incorrect. There appear to be three | separate | pull requests from several years ago that claim to make httpretty.latest_requests a real attribute but none of them have merged in for whatever reason.
With all of that said, I managed to get the list of all previous requests by calling
httpretty.HTTPretty.latest_requests
This returns a list of HTTPrettyRequest objects. Seeing as httpretty.last_request() returns an HTTPrettyRequest object, that attribute is probably what you're looking for.
Unfortunately, .url is not defined on that class (but it is defined on the blank request object which doesn't make any sense). If you want to check that the request URL is what you're expecting, you pretty much have to try reconstructing it yourself:
req = httpretty.HTTPretty.latest_requests[-1]
url = req.headers.get('Host', '') + req.path
If you're passing anything in the query string, you'll have to reconstruct that from req.querystring although that's not ordered so you probably don't want to turn that into a string for matching purposes. Also, if all of your requests are going to the same domain, you can leave off the host part and just compare req.path.

Best-practice: automated web API testing

I've written a program in Python, which works with two distinct API to get the data from two different services (CKAN and MediaWiki).
In particular, there is a class Resource, which requests the data from the above mentioned services and process it.
At some point I've come to conclusion, that there is a need for tests for my app.
And the problem is that all examples I've found on web and in books do not deal with such cases.
For example, inside Resource class I've got a method:
def load_from_ckan(self):
"""
Get the resource
specified by self.id
from config.ckan_api_url
"""
data = json.dumps({'id': self.id})
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = config.ckan_api_url + '/action/resource_show'
r = requests.post(url, timeout=config.ckan_request_timeout, data=data, headers=headers)
assert r.ok, r
resource = json.loads(r.content)
resource = resource["result"]
for key in resource:
setattr(self, key, resource[key])
The load_from_ckan method get the data about resource from the CKAN API and assign it to the object. It is simple, but...
My question is: how to test the methods like this? OR What should I test here?
I thought about the possibility to pickle (save) results to HDD. Then I could load it in the test and compare with the object initialized with load_from_ckan(). But CKAN is community-driven platform and such behavior of such tests will be unpredictable.
If there exist any books on philosophy of automated tests (like what to test, what not to test, how to make tests meaningful etc.), please, give me a link to it.
With any testing, the key question really is - what could go wrong?
In your case, it looks like the three risks are:
The web API in question could stop working. But you check for this already, with assert r.ok.
You, or someone else, could make a mistaken change to the code in future (e.g. mistyping a variable) which breaks it.
The API could change, so that it no longer returns the fields or the format you need.
It feels like you could write a fairly simple test for the latter two, depending on what data from this API you actually rely on: for example, if you're expecting the JSON to have a field called "temperature" which is a floating-point Celsius number, you could write a test which calls your function, then checks that self.temperature is an instance of 'float' and is within a sensible range of values (-30 to 50?). That should leave you confident that both the API and your function are working as designed.
Typically if you want to test against some external service like this you will need to use a mock/dummy object to fake the api of the external service. This must be configurable at run-time either via the method's arguments or the class's constructor or another type of indirection. Another more complex option would be to monkey patch globals during testing, like "import requests; request.post = fake_post", but that can create more problems.
So for example your method could take an argument like so:
def load_from_ckan(self, post=requests.post):
# ...
r = post(url, timeout=config.ckan_request_timeout, data=data,
headers=headers)
# ...
Then during testing your would write your own post function that returned json results you'd see coming back from ckan. For example:
def mock_post(url, timeout=30, data='', headers=None):
# ... probably check input arguments
class DummyResponse:
pass
r = DummyResponse()
r.ok = True
r.content = json.dumps({'result': {'attr1': 1, 'attr2': 2}})
return r
Constructing the result in your test gives you a lot more flexibility than pickling results and returning them because you can fabricate error conditions or focus in on specific formats your code might not expect but you know could exist.
Overall you can see how complicated this could become so I would only start adding this sort of testing if you are experiencing repeated errors or other difficulties. This will just more code you have to maintain.
At this point, you can test that the response from CKAN is properly parsed. So you can pull the JSON from CKAN and ensure that it's returning data with the attributes you're interested in.

Categories

Resources