How to mock API request used inside function in a Django test? - python

I've got a little utility function built like this to grab data from another applications API:
# app/utils.py
import json
import requests
from django.conf import settings
def get_future_assignments(user_id):
"""gets a users future assignments list from the API
Arguments:
user_id {int} -- user_id for a User
"""
headers = {
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
}
api_app = settings.ASSIGNMENTS_API_ROOT_URL # http://project.org/appname/
api_model = "futureassignments/"
api_query = "?user_id=" + str(user_id)
json_response = requests.get(
api_app + api_model + api_query, headers=headers, verify=False
)
return json.loads(json_response.content)
It basically builds the API call and returns the response data - I'd like to test this.
# tests/test_utils.py
import mock
from unittest.mock import patch, Mock
from django.test import TestCase
from app.utils import get_future_assignments
class UtilsTest(TestCase):
def setUp(self):
self.futureassignments = [
{
"id": 342,
"user_id": 18888,
"job": 361,
"location": "1234",
"building": "Building One",
"confirmed_at": None,
"returning": None,
"signature": None,
},
{
"id": 342,
"user_id": 18888,
"job": 361,
"location": "1235",
"building": "Building Two",
"confirmed_at": None,
"returning": None,
"signature": None,
},
]
#patch("app.utils.get_future_assignments")
def test_get_future_assignments_with_multi_assignments(self, mock_gfa):
"""
Test for getting future assignments for a user with mocked API
"""
mock_gfa.return_value = Mock()
# set the json response to what we're expecting
mock_gfa.return_value.json.return_value = self.futureassignments
assignments = get_future_assignments(18888)
self.assertEqual(len(assignments), 2)
It keeps giving me an error that it can't reach the API to get a response (which is expected at the moment - since I'm running it locally and it cannot hit the API)
I'm new to using Mock - so maybe I'm way, way off base here.
Any ideas?

Like you, I am also new to using mock. I believe it's intended to work like this:
#patch("requests.get")
def test_get_future_assignments_with_multi_assignments(self, mock_requests_get):
"""
Test for getting future assignments for a user with mocked API
"""
mock_requests_get.return_value = json.dumps(self.futureassignments)
assignments = get_future_assignments(18888)
self.assertEqual(len(assignments), 2)
Please correct me if I'm wrong!

Related

Error AttributeError: 'function' object has no attribute 'json' occures when importing functions from other files

I'm writting an automated test which uses 3 files helpers.py where I store fucntion of registering, test_mails.py where the test is executed and it runs checking for registration for 5 mails in list, login_credentials.py where json dictionaries are stored. test_mails.py uses unittest library and import main sing_up function for trying to register 5 emails through api requests. The aim of test is to check if system doesn't let these mails to pass. However, when in test_mails.pyI try to use methods from libs json orrequesrts with sing_up function I get these types of errors. AttributeError: 'function' object has no attribute 'json'. This also applies to status_code method How to solve it?
Here are files:
helpers.py
import json
import requests
def sign_up(data):
with requests.Session() as session:
sign_up = session.post(
f'https://siteexample/api/register',
headers={
'content-type': 'application/json',
},
data=json.dumps(data)
)
print(sign_up.content)
print(sign_up.status_code)
test.mails.py
import unittest
import requests
import json
from login_credentials import data, emails_list
from helpers import *
class Mails(unittest.TestCase):
def test_sign_up_public_emails(self):
emails_dict_ls = {}
for email in emails_list:
data_copy = data.copy()
data_copy["email"] = email
emails_dict_ls.update(data_copy)
sign_up(emails_dict_ls)
if 'email' in sign_up.json() and "User with this Email already exists." in
sign_up.json()['email']:
registering = False
else:
registering = True
assert self.assertEqual(sign_up.status_code, 400)
assert sign_up.json()['status_code'] == "NOT_CONFIRMED_EMAIL"
return registering
login_credentials.py
data = {
"company": "Company",
"phone": "+1111111",
"email": "",
"password1": "defaultpassword",
"password2": "defaultpassword",
"terms_agree": True,
"first_name": "TestUser",
"last_name": "One"
}
emails_list = ['mailtest.unconfirm#yahoo.com',
'mailtest.unconfirm#gmail.com',
'mailtest.unconfirm#ukr.net',
'mailtest.unconfirm#outlook.com',
'mailtest.unconfirm#icloud.com'
]
In the sign_up function in helpers.py. You'll have to return the response so that .json() can be called.
Also, you can use json=data instead of data=json.dumps(data) since it's already a built-in function of python-requests.
def sign_up(data):
with requests.Session() as session:
response = session.post(
f'https://siteexample/api/register',
headers={
'content-type': 'application/json',
},
json=data
)
return response
In test_mails.py, You're just calling the function and not storing the value in a variable.
def test_sign_up_public_emails(self):
emails_dict_ls = {}
for email in emails_list:
data_copy = data.copy()
data_copy["email"] = email
emails_dict_ls.update(data_copy)
sign_resp = sign_up(emails_dict_ls)
if 'email' in sign_resp.json()

Flask is caching the returned response

i have an app that return a json response (no HTML).
the problem is the response get cached like the following.
from flask import jsonify, make_response
# base api template
baseApi = {'status':None, 'message':None, 'data':{}}
#app.route('/getdata1')
def getData1():
result = baseApi.copy()
result['status'] = 'success'
result['data'] = {'data1':'some data'}
return make_response(jsonify(result), 200)
#app.route('/getdata2')
def getData2():
result = baseApi.copy()
result['status'] = 'success'
result['data'] = {'data2':'some other data'}
return make_response(jsonify(result), 200)
if i go to the link /getdata1 then the rsponse will be as i expected
{
"data": {
"data1": "some data"
},
"message": null,
"status": "success"
}
but if after that i run the link /getdata2 i got the two results back
{
"data": {
"data1": "some data",
"data2": "some other data"
},
"message": null,
"status": "success"
}
NOTE : the cache not happen in the browser , i have a mobile app , if i go to /getdata1 in the mobile and then try /getdata2 in the PC i got the same thing.
i tried this but it not seem to work !
#api.after_request
def after_request(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, public, max-age=0"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
thanks a lot!!
i guess you should try to use a deepcopy instead of shallow copy of your dictionnary.
the problem is not the cash but rather the dictionary has the same reference :
if we read about the definition of shallow copy
A shallow copy creates a new object which stores the reference of the original elements.
To get a fully independent copy of an object you can use the copy.deepcopy() which is the case of this question.

Trying to call last.fm Rest api for track.getSimilar, in python unable to comprehend the response

Here's my code:
import requests
r = requests.get('http://www.last.fm/api/show/track.getSimilar', params = {
"track": "Believe",
"artist": "Cher",
"limit": "5",
"api_key":"#my_api_key"})
r.status_code returns 200 which means call was successful. However when I try to print the response using r.text, I am not able to comprehend the response.
You are trying to call the documentation URL; the returned page explains how to use the API< it is not the actual API method itself. Call the actual API at:
http://ws.audioscrobbler.com/2.0/
and give the method as a parameter:
params = {
"method": "track.getSimilar",
"track": "Believe",
"artist": "Cher",
"limit": "5",
"api_key":"#my_api_key"
}
r = request.get('http://ws.audioscrobbler.com/2.0/', params=params)
See the API introduction:
The API root URL is located at http://ws.audioscrobbler.com/2.0/
and the REST requests documentation:
Generally speaking, you will send a method parameter expressed as 'package.method' along with method specific arguments to the root URL. The following parameters are required for all calls:
api_key : A Last.fm API Key.
method : An API method expressed as package.method, corresponding to a documented last.fm API method name.
Demo:
>>> import requests
>>> params = {
... "method": "track.getSimilar",
... "track": "Believe",
... "artist": "Cher",
... "limit": "5",
... 'api_key': '#a valid api key#',
... }
>>> r = requests.get('http://ws.audioscrobbler.com/2.0/', params=params)
>>> r
<Response [200]>
>>> r.headers['content-type']
'text/xml; charset=utf-8;'
>>> r.content.splitlines()[1:3]
['<lfm status="ok">', '<similartracks track="Believe" artist="Cher">']
If you are using requests it may be easier to set the format parameter to json:
>>> params['format']= 'json'
>>> r = requests.get('http://ws.audioscrobbler.com/2.0/', params=params)
>>> r.json()['similartracks']['#attr']
{u'track': u'Believe', u'artist': u'Cher'}
However, rather than re-invent the wheel, you could use the pyLast module instead:
import pylast
from itertools import islice
last = pylast.LastFMNetwork(api_key="#your_api_key", api_secret="#your_api_secret")
track = last.get_track('Cher', 'Believe')
for similar in islice(track.get_similar(), 5):
# limited to the first 5 similar tracks
print similar.item

Python: Mocking DateTime Issues

Here is my Python method:
for time in (timedelta(hours=3), timedelta(minutes=30)):
delay = (datetime.now() + time).strftime('%Y-%m-%dT%H:%M:%S')
payload = json.dumps({
"channels": [
"accountID-{acct_id}".format(acct_id=account_id)
],
"push_time": delay,
"data": {
"alert": "Test Push",
"m": "12345"
},
})
try:
requests.post(
"https://api.parse.com/1/push",
data=payload,
headers={
"X-Parse-Application-Id": settings.PARSE_APPLICATION_ID,
"X-Parse-REST-API-Key": settings.PARSE_REST_API_KEY,
"Content-Type": "application/json"
}
)
except requests.exceptions.RequestException as e:
logging.getLogger().setLevel(logging.ERROR)
Here is my test:
#patch("requests.post")
def test_send_push_notifications_to_parse(self, post_mock):
post_mock.return_value = {"status": 200}
mailing = Mock()
mailing.name = "Foo Name"
mailing.account_id = 12345
mailing.mailing_id = 45
payload = json.dumps({
"channels": [
"accountID-12345"
],
"push_time": "2014-03-04T15:00:00",
"data": {
"alert": "Test Push",
"m": "12345"
},
})
send_push_notification_to_parse(mailing.account_id, mailing.mailing_id, mailing.name)
post_mock.assert_called_with(
"https://api.parse.com/1/push",
data=payload,
headers={
"X-Parse-Application-Id": settings.PARSE_APPLICATION_ID,
"X-Parse-REST-API-Key": settings.PARSE_REST_API_KEY,
"Content-Type": "application/json"
}
)
The test fails because the POST request is inside of a loop where the datetimeobject changes. How can I patch the datetime object to make my test pass?
Just mock datetime in your module:
#patch("your.module.datetime")
#patch("requests.post")
def test_send_push_notifications_to_parse(self, post_mock, dt_mock):
# set datetime.now() to a fixed value
dt_mock.now.return_value = datetime.datetime(2013, 2, 1, 10, 9, 8)
You bound datetime in your module with an import, and the above #patch decorator will replace that object with a mock.
If you need to test against multiple values, you can set the dt_mock.now.side_effect attribute instead; a list will cause the mock to return values one by one from that list on sequential calls, a method lets you produce a new value each time datetime.now() is called as needed.
Anyone stumbling on this old question like me can use freezegun that Lets your Python tests travel through time(like Interstellar:))

Django/Python testing using Mock for third party API

I have a code which I am trying to test using Mock library/python-django. To give brief summary of my application is:
(PHASE I): Client use my app exposed API. The mapped API function make a HTTP Connection request to 3rd party API ( Tropo which are CaaS provider)
(PHASE II): Tropo server(3rd party) respond back with some url back to my server which I mapped to function which send another request to Tropo Server which on their side, make call() to phonenumbers.
I used Django test client by just using my API which does run but the problem is it also reply on Tropo to make real call to the numbers I give in. So I thought to use mock() library but I have very little knowledge of it.
What I did is, I see what response I get after first phase from Tropo hard coded it in variable input and I also have expected_output variable which is also hardcoded seeing the output after second phase.
But I want to architect my test framework properly in which I can mock the whole tropo library in my testing environment and all the request go to this fake library and not the real tropo. But not changing the code.
Any ideas or suggestion. Please bare me as I am developer and this is something I try to do for testing.
Since I am not getting any response I am trying to give more details of what exactly I am stuck in:
Let say there this snippet of my code in one function...
conn = httplib.HTTPConnection('Some_External_URI')
headers = {"accept": "application/json", "Content-type":"application/json"}
params = ''
conn.request('POST', data, params, headers)
response = conn.getresponse()
payload = response.read()
How I can mock this particular connection request?
I was able to reach at some level of testing by mocking class in my code.
test.py
from mock import patch, MagicMock
from tropo.conferencing import TropoConferencing
#patch.object(TropoConferencing, 'sendTriggerCallRequest')
def test_ConferenceCreation(self, sendTriggerCallRequest):
response_conference = self._createConference(sendTriggerCallRequest)
self.assertContains(response_conference, 200)
def _createConference(self, sendTriggerCallRequest):
self._twoParticipants_PhaseI(sendTriggerCallRequest)
response_conference = self.client.post(self.const.create_conferenceApi , {'title':self.const.title,'participants':self.const.participants})
obj = json.loads(response_conference.content)
self.conf_id = obj['details']['conference_id']
try:
conference_id = Conference.objects.get(conferenceId=self.conf_id)
except Conference.DoesNotExist:
print 'Conference not found'
# PHASE II
self._twoParticipants_PhaseII()
return response_conference
def _twoParticipants_PhaseI(self, sendTriggerCallRequest):
list_of_return_values= [{'json': {'session_id': 'e85ea1229f2dd02c7d7534c2a4392b32', 'address': u'xxxxxxxxx'}, 'response': True},
{'json': {'session_id': 'e72bf728d4de2aa039f39843097de14f', 'address': u'xxxxxxxx'}, 'response': True}
]
def side_effect(*args, **kwargs):
return list_of_return_values.pop()
sendTriggerCallRequest.side_effect = side_effect
def _twoParticipants_PhaseII(self):
input = {"session":{"id":"e72bf728d4de2aa039f39843097de14f","accountId":"xxxxx","timestamp":"2013-01-07T18:32:20.905Z","userType":"NONE","initialText":'null',"callId":'null',"parameters":{"phonenumber":"xxxxxxx","action":"create","conference_id":str(self.conf_id),"format":"form"}}}
expectedOutput = '{"tropo": [{"call": {"to": "xxxxxxx", "allowSignals": "exit", "from": "xxxxxxx", "timeout": 60}}, {"ask": {"name": "join_conference", "say": {"value": "Please press one to join conference"}, "choices": {"terminator": "*", "value": "1", "mode": "dtmf"}, "attempts": 1, "timeout": 5, "voice": "Susan"}}, {"on": {"event": "mute", "next": "' + self.const.muteEvent+ str(self.conf_id) + '/xxxxxx"}}, {"on": {"event": "unmute", "next": "/conference/rest/v1/conference/events/unmute/'+ str(self.conf_id) + '/xxxxxxx"}}, {"on": {"event": "hangup", "next": "'+ str(self.conf_id) + '/xxxxxx"}}, {"on": {"event": "continue", "next": "'+ str(self.conf_id) + '/xxxxxx"}}, {"on": {"event": "exit", "next": "'+ str(self.conf_id) + '/xxxxxx"}}, {"on": {"event": "error", "next": "/conference/rest/v1/conference/events/hangup/'+ str(self.conf_id) + '/xxxxxxx"}}, {"on": {"event": "incomplete", "next": "'+ str(self.conf_id) + '/xxxxxxx"}}, {"say": {"value": ""}}]}'
callbackPayload = json.dumps(input)
request = MagicMock()
request.raw_post_data = callbackPayload
response = call(request)
self.assertEqual(response.content, expectedOutput)
As you can see I am hardcoding the response I get form Tropo and passing onto the function. Please let me know if any QA have better solution to this type of problem

Categories