assert JSON response - python

i have an python API call, and the server response is coming to me as JSON output.
How can I assert that the "status" from this output is 0 for example:
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
assert req["status"]="0"
is what i've tried.
The response is looking like:
{"status" : 0, ......}
error i got is:
TypeError: 'Response' object has no attribute '__getitem__'

If you simply need to check that the request was successful, using request.status_code will do the trick:
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
assert req.status_code == 200
If you want instead to check for the presence of a specific key-value pair in the response, you need to convert your response payload from json to a dict:
import json
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
data = json.loads(req.content)
assert data["status"] == "0"
If you are using the Requests library you can avoid converting json manually by using its builtin json decoder.

It should be assert req['status'] == 0, i. e. comparison (==) instead of assignment (=) and 0 as integer not "0" as string (not entirely sure about the latter).

status code in assertion:
response.ok #it is True if response status code is 200.
In context with pytest it would be like that:
#pytest.mark.parametrize("fixture_name", [(path, json)], indirect=True)
def test_the_response_status_code_first(fixture_name):
assert fixture_name.ok, "The message for the case if the status code != 200."
# the same with checking status code directly:
assert fixture_name.status_code == 200, "Some text for failure. Optional."

Related

Pytest - how to structure the tests efficiently

Suppose there are 2 endpoints to be tested.
Endpoint 1 returns data that needs to be used in a request to endpoint 2.
How to get 2 endpoints tested efficiently?
# file_1.py
def test_endpoint_1():
r = requests.get(...)
assert r.status_code == 200
maybe some JSON Schema validation
return r
# file_2.py
from file_1 import test_endpoint_1
def test_endpoint_2():
needed_data = test_endpoint_1()
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation
Above approach kinda works but if I execute the test suite we are testing endpoint_1 twice. I could change the name of test_endpoint_1() to avoid that but maybe there is an easier and more elegant way?
Accessing endpoint1 is just one way to get a suitable payload for endpoint2. Another is to just provide a hard-coded payload.
# file_2.py
def test_endpoint_2():
needed_data = {"foo": "bar"} # Whatever is appropriate
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation
Hard coded value
Generally, we "assume" that dependencies are working correctly.
In this case, keep it simple and create an hardcoded value in the test.
# file_2.py
def test_endpoint_2():
needed_data = "JSON" # valid return example from test_endpoint_1
r = requests.get(..., payload=needed_data)
assert r.status_code == 200
maybe some JSON Schema validation
pytest.fixture
If you need this value in many tests, create a fixture instead. This will allows you to edit the "valid value" for endpoint_2 in one place instead of changing it in every tests that depend on it.
I advise you to put your fixtures in a conftest.py file. This is explained here.
# conftest.py
import pytest
#pytest.fixture
def endpoint_1_value_200():
return "JSON"
# file_2.py
def test_endpoint_2(endpoint_1_value_200):
r = requests.get(..., payload=endpoint_1_value_200)
assert r.status_code == 200
maybe some JSON Schema validation
Just put it in a separate function :
def check_endpoint_1():
r = requests.get(...)
assert r.status_code == 200
maybe some JSON Schema validation
return r
def test_endpoint_1():
check_endpoint_1()
def test_endpoint_2():
needed_data = check_endpoint_1()
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation

how to solve this "Expecting value: line 1 column 1 (char 0)"?

Request Method: PATCH
There is a Query String Parameter section
Your code cannot be run to reproduce your error. Check what you get as a response here:
r = requests.patch(url, headers=self._construct_header(),data=body)
response = getattr(r,'_content').decode("utf-8")
response_json = json.loads(response)
If you pass invalid json to json.loads(), then an error occurs with a similar message.
import json
response = b'test data'.decode("utf-8")
print(response)
response_json = json.loads(response)
print(response_json)
Output:
test data
Traceback (most recent call last):
...
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
EDIT:
In your case, to avoid an error, you need to add an if-else block. After receiving the response, you need to check what exactly you received.
r = requests.patch(url, headers=self._construct_header(),data=body)
# if necessary, check content type
print(r.headers['Content-Type'])
response = getattr(r,'_content').decode("utf-8")
if r.status_code == requests.codes.ok:
# make sure you get the string "success"
# if necessary, do something with the string
return response
else:
# if necessary, check what error you have: client or server errors, etc.
# or throw an exception to indicate that something went wrong
# if necessary, make sure you get the error in json format
# you may also get an error if the json is not valid
# since your api returns json formatted error message:
response_dict = json.loads(response)
return response_dict
In these cases, your function returns the string "success" or dict with a description of the error.
Usage:
data = {
'correct_prediction': 'funny',
'is_accurate': 'False',
'newLabel': 'funny',
}
response = aiservice.update_prediction(data)
if isinstance(response, str):
print('New Prediction Status: ', response)
else:
# provide error information
# you can extract error description from dict

Python API Client Rerun function if run unsuccessfully

Python Requests API client has a function that needs to re execute if run unsuccessfully.
Kitten(BaseClient):
def create(self, **params):
uri = self.BASE_URL
data = dict(**(params or {}))
r = self.client.post(uri, data=json.dumps(data))
return r
If ran with
api = Kitten()
data = {"email": "bill#dow.com", "currency": "USD", "country": "US" }
r = api.create(**data)
The issue is whenever you run it, the first time it always returns back the request as GET, even when it it POST. The first time the post is sent, it returns back GET list of entries.
The later requests, second and later, api.create(**data) return back new entries created like they should be.
There is a status_code for get and post
# GET
r.status_code == 200
# POST
r.status_code == 201
What would be better Python way to re execute when status_code is 200, till a valid 201 is returned.
If you know for sure that the 2nd post will always return your expected value, you can use a ternary operator to perform the check a second time:
Kitten(BaseClient):
def create(self, **params):
uri = self.BASE_URL
data = dict(**(params or {}))
r = self._get_response(uri, data)
return r if r.status_code == 201 else self._get_response(uri, data)
def _get_response(uri, data):
return self.client.post(uri, data=json.dumps(data)
Otherwise you can put it in a while loop where the condition is that the status code == 201.

Super-performatic comparison

I have a python code which recovers information from an HTTP API using the requests module. This code is run over and over again with an interval of few milliseconds between each call.
The HTTP API which I'm calling can send me 3 different responses, which can be:
text 'EMPTYFRAME' with HTTP status 200
text 'CAMERAUNAVAILABLE' with HTTP status 200
JPEG image with HTTP status 200
This is part of the code which handles this situation:
try:
r = requests.get(url,
auth=(username, pwd),
params={
'camera': camera_id,
'ds': int((datetime.now() - datetime.utcfromtimestamp(0)).total_seconds())
}
)
if r.text == 'CAMERAUNAVAILABLE':
raise CameraManager.CameraUnavailableException()
elif r.text == 'EMPTYFRAME':
raise CameraManager.EmptyFrameException()
else:
return r.content
except ConnectionError:
# handles the error - not important here
The critical part is the if/elif/else section, this comparison is taking way too long to complete and if I completely remove and simply replace it by return r.content, I have the performance I wish to, but checking for these other two responses other than the image is important for the application flow.
I also tried like:
if len(r.text) == len('CAMERAUNAVAILABLE'):
raise CameraManager.CameraUnavailableException()
elif len(r.text) == len('EMPTYFRAME'):
raise CameraManager.EmptyFrameException()
else:
return r.content
And:
if r.text[:17] == 'CAMERAUNAVAILABLE':
raise CameraManager.CameraUnavailableException()
elif r.text[:10] == 'EMPTYFRAME':
raise CameraManager.EmptyFrameException()
else:
return r.content
Which made it faster but still not as fast as I think this can get.
So is there a way to optimize this comparison?
EDIT
With the accepted answer, the final code is like this:
if r.headers['content-type'] == 'image/jpeg':
return r.content
elif len(r.text) == len('CAMERAUNAVAILABLE'):
raise CameraManager.CameraUnavailableException()
elif len(r.text) == len('EMPTYFRAME'):
raise CameraManager.EmptyFrameException()
Checking the response's Content-Type provided a much faster way to assure an image was received.
Comparing the whole r.text (which may contain the JPEG bytes) is probably slow.
You could compare the Content-Type header the server should set:
ct = r.headers['content-type']
if ct == "text/plain":
# check for CAMERAUNAVAILABLE or EMPTYFRAME
else:
# this is a JPEG

How to mock an object returned by a mocked object?

I don't quite have the hang of Python mocks.
Clearly I don't want my test code to call the actual methodrequests.post() in the following method, so I want to to mock its behavior instead:
def try_post(self, url, body):
r = requests.post(url, data=body)
msg = str(r.status_code) + " " + r.content + "\n"
if r.status_code >= 300:
sys.stderr.write("Error: POST returned " + msg)
My question: How do I mock the object returned by requests.post(), i.e. the response object?
For example, I'd like to write one test where r.status_code is 200 and another where r.status_code is 300 so I can test the conditional logic. Also, I'd need to mock r.content to return some string.
My non-working code follows:
from monitor_writer import MonitorWriter
import mock
import unittest
class TestMonitorWriter(unittest.TestCase):
#mock.patch('monitor_writer.requests')
def test_conforming_write(self, mock_requests):
xml_frag = """
<InstantaneousDemand>
</InstantaneousDemand>
"""
mock_requests.status_code.return_value = 200
mock_requests.content.return_value = "OK"
writer = MonitorWriter()
writer.update(xml_frag)
self.assertTrue(mock_requests.post.called, "Failed to call requests.post")
This test fails with TypeError: expected a character buffer object because r.status_code and r.content evaluate to mock.MagicMock objects, not strings, and the try_post() method is attempting to catenate them.
You want to mock requests.post directly, rather than the entire requests module:
class TestMonitorWriter(unittest.TestCase):
#mock.patch('monitor_writer.requests.post')
def test_conforming_write(self, mock_post):
xml_frag = """
<InstantaneousDemand>
</InstantaneousDemand>
"""
response = mock.MagicMock()
response.status_code = 200
respone.content = "OK"
mock_post.return_value = response
writer = MonitorWriter()
writer.update(xml_frag)
self.assertTrue(mock_post.called, "Failed to call requests.post")
Once we've mocked the function you're actually calling, we create a mock response object, set status_code and content on the mocked response to the desired values, and finally assign the mocked response to the return_value of our mocked post function.

Categories