Pytest mock.patch requests AttributeError: does not have the attribute 'json' - python

I'm trying to test an api mock call using from unittest.mock import patch. I keep getting an AttributError when I include .json() in my function's response return (i.e. return response.json()).
When I exclude the .json() in my return statement, the test passes. I'm pretty new to testing and I can't figure out how to get around this error. Does anyone have an idea of how to get around this by keeping the .json() in the return statment?
Here is my code for reference:
test_pytest.py
from unittest.mock import patch
from src.open_play_helper import get_open_play_data
#patch('requests.get',
return_value={
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
)
def test_get_open_play_data(mock_get, mock_json):
print("Mock_get: ", mock_get())
print("Mock_json: ", mock_json())
print("Function: ", get_open_play_data)
# print("Function call: ", get_open_play_data('https://connect.secretlyportal.com/v1/',
# OP_CONFIG['OP_KEY'],
# OP_CONFIG['OP_PASSWORD']))
result = get_open_play_data(
'route', 'key', 'password'
)
print("Result: ", result)
assert mock_get() == result
assert False
open_play_helper.py
import requests
def get_open_play_data(route, key, password) -> object:
# route should be a string
try:
response = requests.get(
route,
auth=(
key,
password
)
)
print("Response: ", response)
return response.json()
except requests.exceptions.RequestException as e:
print(f"There was a problem with your request: '{e}'")

As #LuรญsMรถllmann already pointed out, the patching is incorrect. The actual usage was:
requests.get().json()
But the patching was:
requests.get.return_value = {some dict}
This means that requests.get() will already return {some dict} which then fails when .json() is called.
Solution 1
The dictionary response must be mocked at requests.get.return_value.json.return_value and not just the requests.get.return_value:
#patch('requests.get')
def test_get_open_play_data(mock_get):
mock_json = {
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
mock_get.return_value.json.return_value = mock_json
result = get_open_play_data(
'route', 'key', 'password'
)
print("Result: ", result)
assert mock_json == result
Solution 2 (Recommended)
Don't reinvent the wheel of mocking the requests module. Use a library such as requests_mock which does it easily for you.
import requests_mock as requests_mock_lib
def test_get_open_play_data_using_lib(requests_mock):
mock_json = {
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
requests_mock.get("http://route", json=mock_json) # If you want the mock to be used on any URL, replace <"http://route"> with <requests_mock_lib.ANY>
result = get_open_play_data(
'http://route', 'key', 'password'
)
print("Result: ", result)
assert mock_json == result

Your mock returns a dictionary, not a response object like the original requests.get function:
#patch('requests.get',
return_value={
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
)
So basically, you're doing:
{"version":"v1","greeting":"Aloha ๐Ÿ‘‹"}.json()
Hence the error:
'dict' object does not have the attribute 'json'

Related

How to mock HTTPError response in Python unit test

I would like to write a unit test case for HTTPError exception part based on the error response content I get. But I have now idea how I can mock the response so that the unit test can reach doSomething1() instead of doSomething2().
foo.py
def get_result_from_API():
#Try to call API here...
def getSomething():
try:
result = get_result_from_API()
except HTTPError as error:
error_json = error.response.json()
if error_json.get("error").get("code") == "00001":
doSomething1()
else:
doSomething2()
raise error
Unit Test
#patch('foo.doSomething2')
#patch('foo.doSomething1')
#patch('foo.get_result_from_API')
def testGetSomething(get_result_from_API,doSomething1,doSomething2):
mock_response = Mock()
mock_response.return_value = {
"error":{
"code": "00001",
"message": "error message for foo reason"
}
}
get_result_from_API.side_effect = HTTPError(response=mock_response)
with self.assertRaises(HTTPError):
foo.getSomething()
doSomething1.assert_called_once()
The current result is that doSomething1() is not called where as doSomething2() is called.
Since the getSomething function calls:
error_json = error.response.json()
the solution is to mock the call to .json()
mock_response.json.return_value = {
"error":{
"code": "00001",
"message": "error message for foo reason"
}
}
As it was originally written, getSomething would have had to have called response() directly.
error_json = error.response()
Here is a more generic example:
>>> from unittest.mock import Mock
>>> mock_response = Mock()
>>> mock_response.return_value = {"hello": "world"}
>>> mock_response() # call response() directly
{'hello': 'world'}
>>> mock_response.json() # no return_value is assigned to the json() call
<Mock name='mock.json()' id='4407756744'>
>>> mock_response.json.return_value = {"hello": "universe"} # assign a json() return_value
>>> mock_response.json()
{'hello': 'universe'}
If you want, you can even do this while instantiating Mock:

how to mock jwt.decode inside aws lambda for unitesting

I have created a lambda that will receive JWT in lambda's event header and subsequently decode the jwt payload and give me the subject.
lambda snippet looks like this:
def handler(event, context):
print("hello world!")
# print(event)
message = dict()
jwt_token = event['headers']['x-jwt-token']
# more info on PyJWT
# https://pyjwt.readthedocs.io/en/latest/index.html
try:
decoded_jwt = jwt.decode(jwt_token,
options={"verify_signature": False})
except jwt.ExpiredSignatureError:
print("Signature expired. Get new one!!!")
message['Body'] = {
'Status': 'Lambda failure',
'Reason': 'JWT Signature expired. Get new one!!!'
}
except jwt.InvalidTokenError:
print("Invalid Token")
message['Body'] = {
'Status': 'Lambda failure',
'Reason': 'JWT Invalid Token'
}
else:
# all are good to go
if event['httpMethod'] == 'GET':
resource_owner_name = "".join(decoded_jwt["subject"])
now I have created the below fixtures for my unit tests:
sample_events.json
{
"resource": "/path",
"path": "/path'",
"httpMethod": "GET",
"headers": {
"x-jwt-token": "welcome.here.1234"
}
}
and in my test_main.py
def load_json_from_file(json_path):
with open(json_path) as f:
return json.load(f)
#pytest.fixture
def events():
return load_json_from_file('unit_tests/fixtures/sample_events.json')
def test_main(events, context):
# jwt = Mock()
# jwt.decode.return_value =
response = handler(events, context)
Now I wonder how to bind the jwt and mock it in my python handler? what is the solution, is there any other approach which I could follow?
I also tried to patch the Jwt.decode still no luck...anyone can shed some light on patching the jwt decode that might help?
You can use patch to patch a given target:
def test_main(events, context):
with patch("handler.jwt.decode") as decode_mock:
decode_mock.return_value =
response = handler(events, context)
Just make sure you path the correct path to the patch target.

ValueError: not enough values to unpack while running unit tests Django ModelViewSet

Am testing an endpoint that retrieves data using a ModelViewSet, and am passing a param via a URL to it to get data but am getting this error when I run the unit tests:
File "/Users/lutaayaidris/Documents/workspace/project_sample/project_sample/financing_settings/tests.py", line 195, in test_get_blocks
self.block_get_data), content_type='application/json')
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/rest_framework/test.py", line 286, in get
response = super().get(path, data=data, **extra)
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/rest_framework/test.py", line 194, in get
'QUERY_STRING': urlencode(data or {}, doseq=True),
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/django/utils/http.py", line 93, in urlencode
for key, value in query:
ValueError: not enough values to unpack (expected 2, got 1)
This is how I have structured my tests , plus some dummy data for testing :
class TemplateData:
"""Template Mock data."""
step_get_data = {
"param": "step"
}
block_get_data = {
"param": "block"
}
get_no_data = {
"param_": "block"
}
class TemplateViewTests(TestCase, TemplateData):
"""Template Tests (Block & Step)."""
def setUp(self):
"""
Initialize client, Step and Block id and data created.
"""
self.client = APIClient()
self.block_id = 0
self.step_id = 0
self.create_block_step_data()
def create_block_step_data(self):
"""Create ProcessVersion, Step, & Block mock data."""
self.process_version = ProcessVersion.objects.create(
tag="TESTING_TAG",
is_process_template=False,
status="IN EDITING",
attr_map="TESTING_ATTR",
loan_options=None
)
self.step = Step.objects.create(
version=self.process_version,
is_process_template=True,
title="TESTING",
help_text="TESTING",
order=1,
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
)
self.step_id = self.step.pk
self.block_id = Block.objects.create(
step=self.step,
is_process_template=True,
title="TESTING",
information_text="This is testing "
"information",
order=1,
depending_field="depending_field",
visibility_value="visibility_value",
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
).pk
self.process_version_1 = ProcessVersion.objects.create(
tag="TESTING_TAG",
is_process_template=False,
status="IN EDITING",
attr_map="TESTING_ATTR",
loan_options=None
)
self.step_1 = Step.objects.create(
version=self.process_version_1,
is_process_template=True,
title="TESTING",
help_text="TESTING",
order=1,
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
)
self.block_1 = Block.objects.create(
step=self.step,
is_process_template=True,
title="TESTING",
information_text="This is testing "
"information",
order=1,
depending_field="depending_field",
visibility_value="visibility_value",
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
).pk
def test_get_blocks(self):
"""Test get list of Block. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data), content_type='application/json')
self.assertEqual(response.status_code, 200)
def test_get_steps(self):
"""Test get list of Step. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data),
content_type='application/json')
self.assertEqual(response.status_code, 200)
def test_no_step_or_block(self):
"""Test get no list of Step or Block. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data),
content_type='application/json')
self.assertEqual(response.status_code, 204)
As you can see above those are my tests, I have already setup the data , now I want to retrieve back the data, but because of the exception above I can't.
Lastly, in my endpoint implementation, I used a Viewset to handle this , below is the code :
class TemplateView(ModelViewSet):
"""ViewSet for Saving Block/ Step template."""
def list(self, request, *args, **kwargs):
"""Get list of Block/Steps with is_process_template is equal to True."""
param = request.data['param']
if param == "block":
_block = Block.objects.filter(is_process_template=True).values()
return JsonResponse({"data": list(_block)}, safe=False, status=200)
elif param == "step":
_step = Step.objects.filter(is_process_template=True)
return JsonResponse({"data": list(_step)}, safe=False, status=200)
return Response(status=status.HTTP_204_NO_CONTENT)
What is causing this , in my understanding I feel like everything should work.
The function Client.get expect a dictionary as data argument and try to encode it in the url using the function urlencode. You could do something like that:
from django.test import Client
c = Client()
block_get_data = {
"param": "block"
}
c.get('path', block_get_data)
block_get_data will be sent in the url as 'param=block'
If you want to send JSON formated data in a GET method, you can use Client.generic function as follow:
from django.test import Client
import json
c = Client()
block_get_data = {
"param": "block"
}
c.generic('GET', 'path', json.dumps(block_get_data), 'application/json')
You are facing this error because this dict
block_get_data = {
"param": "block"
}
you are trying to use it in this way
for key,val in block_get_data
and it will produce the error like
for key,val in block_get_data:
ValueError: too many values to unpack (expected 2)
It will be solved if your loop through dict by using .items() method.
for key,val in block_get_data.items():
I think by passing parameter as self.block_get_data.items() may solve your problem.

How to test and mock mongodb with flask?

I'm trying to do a test of an endpoint post, which sends information in json to a mongo database.
How do I perform a unit test in this case? How can I mock mongo to have a momentary database?
Below is my code. Note that if I don't have the mongo connected, I get the error that it was not possible to send the data, which is notorious. My question is, how can I perform this test by mocking the mongo?
import json
import unittest
from api import app
app.testing = True # set our application to testing mode
class TestApi(unittest.TestCase):
with app.test_client() as client:
# send data as POST form to endpoint
sent = {
"test1": 1,
"test2": 1,
"test3": 1
}
mimetype = 'application/json'
headers = {
'Content-Type': mimetype,
}
#fixtures
result = client.post(
'/post/',
data=json.dumps(sent), headers=headers, environ_base={'REMOTE_ADDR': 'locahost'})
def test_check_result_server_have_expected_data(self):
# check result from server with expected data
self.assertEqual(self.result.json, self.sent)
def test_check_content_equals_json(self):
# check content_type == 'application/json'
self.assertEqual(self.result.content_type, self.mimetype)
if __name__ == "__main__":
unittest.main()
My api calls mongo in that way:
#api.route('/post/')
class posting(Resource):
#api.doc(responses={200: 'Success', 500: 'Internal Server Error', 400: 'Bad Request'})
#api.doc(body=fields, description="Uploads data")
#api.expect(fields, validate=True)
def post(self):
try:
mongodb_helper.insert_metric(name="ODD", timestamp=request.json["timestamp"],
metric_type="field1", value=request.json["field1"])
return ("Data saved successfully", 201)
except:
return ("Could not create new data", 500)
Thanks,
Here's an example of using unittest.mock.Mock and the patch context manager. You will of course need to replace "__main__.insert_metric" with something more suitable.
import unittest.mock
# Function to be mocked
def insert_metric(**kwargs):
raise ValueError("this should never get called")
# Function to be tested
def post(data):
insert_metric(
name="ODD", timestamp=data["timestamp"], metric_type="field1", value=data["field1"]
)
# Test case
def test_post():
insert_metric_mock = unittest.mock.MagicMock()
with unittest.mock.patch("__main__.insert_metric", insert_metric_mock):
post({"timestamp": 8, "field1": 9})
print(insert_metric_mock.mock_calls) # for debugging
insert_metric_mock.assert_called_with(
name="ODD", timestamp=8, metric_type="field1", value=9
)
if __name__ == "__main__":
test_post()
This prints out
[call(metric_type='field1', name='ODD', timestamp=8, value=9)]
and doesn't raise an assertion error.

How to fake my response in pytest requests mock

My function that I am trying to test is returning list of strings:
def listForumsIds:
response = requests.get(url)
forums= response.json().get('forums')
forumsIds= [forum['documentId'] for forum in forums]
# return like: ['id1', 'id2', 'id3'.....]
return forumsIds
My test function:
#requests_mock.mock()
def test_forms(self, m):
# I also used json='response'
m.get('valid url', text="response", status_code=200)
resp = listForumsIds('valid url')
# ERROR !!!!
assert resp == "response"
I am getting error like: json.decoder.JSONDecodeError or str object has no attribute get
How to fake my response to be match return value of my function?
You have to pass the desired payload in the json field of the mocked response. Example, adapted to your code:
class MyTests(unittest.TestCase):
#requests_mock.mock()
def test_forms(self, m):
payload = {"forums": [{"documentId": "id1"}]}
m.register_uri("GET", "https://www.example.com", json=payload)
ids = listForumsIds('https://www.example.com')
assert ids == ['id1']

Categories