I have to test out a certain view that gets certain information from request.args.
I can't mock this since a lot of stuff in the view uses the request object.
The only alternative I can think of is to manually set request.args.
I can do that with the test_request_context(), e.g:
with self.app.test_request_context() as req:
req.request.args = {'code': 'mocked access token'}
MyView()
Now the request inside this view will have the arguments that I've set.
However I need to call my view, not just initialize it, so I use this:
with self.app.test_client() as c:
resp = c.get('/myview')
But I don't know how to manipulate the request arguments in this manner.
I have tried this:
with self.app.test_client() as c:
with self.app.test_request_context() as req:
req.request.args = {'code': 'mocked access token'}
resp = c.get('/myview')
but this does not set request.args.
Pass the query_string argument to c.get, which can either be a dict, a MultiDict, or an already encoded string.
with app.test_client() as c:
r = c.get('/', query_string={'name': 'davidism'})
The test client request methods pass their arguments to Werkzeug's EnvironBuilder, which is where this is documented.
Related
I' trying to properly test this simple function:
def get_content_from_header(request, header_name):
try:
content = request.headers[header_name]
except KeyError:
logging.error(f"BAD REQUEST: '{header_name}' header is missing from the request.")
except AttributeError:
logging.error(f"BAD REQUEST: request has no attributes 'headers'.")
else:
return content
return None
So this is my code so far, I'm using parametrize along with fixture to achieve my goal:
import main
import pytest
class ValidRequest:
def __init__(self):
self.headers = {
'Authorization': 'test_auth'
}
#pytest.fixture
def mocked_request():
request = ValidRequest()
return request
#pytest.mark.parametrize("possible_input, expected_output",
[('Authorization', 'test_auth'),
('InvalidHeader', None)])
def test_get_content_from_header(mocked_request, possible_input, expected_output):
# Run the function with mocked request
assert main.get_content_from_header(mocked_request, possible_input) == expected_output
Here's my problem: I only test the second parameter of the function get_content_from_header, not request which is the first one. How could I properly do that ?
Should I create a new class InvalidRequest and test my function with this new class in a new testing function just below test_get_content_from_header ?
Or should I add this new parameter trough parametrize in the existing testing function ?
What is the cleanest (more pythonic) way to do it ?
I would suggest a little change here, lets simplify that function a bit. Since that we are getting a certain header from the headers dict of the request we can just pass just the headers dict instead of the whole request.
def get_content_from_header(headers: dict, header_name: str):
if header_name in headers.keys():
return headers[header_name]
return None
This works the same way as your function, and you do not have to test your request parameter. Now you can test that in a very simple manner:
def test_get_content_from_header_returning_header_value():
headers = {"Authorization": "test_auth"}
assert get_content_from_header(headers, "Authorization") == "test_auth"
def test_get_content_from_header_returning_none():
headers = {"Authorization": None}
assert get_content_from_header(headers, "Authorization") == None
Now you don't need to test your request in that test, you can refer to https://flask.palletsprojects.com/en/2.0.x/testing/ and more specifically the client usage and test your endpoints, to test your request param.
Now about the loggers, I will usually place those in the place where you actually call the get_content_from_header function.
How can I mock a post inside a method, so i can have unittests?
def send_report(self, data):
url = settings.WEBHOOK_PO
payload = json.dumps(data)
requests.post(url, data=payload)
url = settings.WEBHOOK_LQA
response = requests.post(url, data=payload)
return response.status_code
Is there a way to cover this method for unit test with not actually posting?
You can use the mock library to replace requests.post with something else:
with mock.patch('requests.post') as mock_post:
foo.send_report(data)
(mock is a third-party package, but was added to the standard library, as part of the unittest package`, in Python 3.3.)
mock_post can be configured to provide the desired behavior during the test; consult the mock documentation for details.
Another option is to modify your method to take the post function as an argument, rather than hard-coding the function (this is an example of dependency injection):
def send_report(self, data, poster=requests.post):
url = settings.WEBHOOK_PO
payload = json.dumps(data)
poster(url, data=payload)
url = settings.WEBHOOK_LQA
response = poster(url, data=payload)
return response.status_code
When you want to test the function, you simply pass a different callable object as the optional second argument.
Note that you can supply separate functions for the two types of posts, which might make it easier to test than with a mock:
from functools import partial
def send_report(self,
data,
post_po=partial(requests.post, settings.WEBHOOK_PO),
post_lqa=partial(requests.post, settings.WEBHOOK_LQA)):
payload = json.dumps(data)
post_po(data=payload)
response = post_lqa(data=payload)
return response.status_code
I already have a REST API with GAE python built using webapp2. I was looking at protorpc messages used in protorpc and Cloud Enpoints and really like how I can define the request and responses. Is there a way to incorporate that into my webapp2 handlers?
Firstly I use a decorator on the webapp2 method. I define the decorator as follows*:
# Takes webapp2 request (self.request on baseHandler) and converts to defined protoRpc object
def jsonMethod(requestType, responseType, http_method='GET'):
"""
NB: if both URL and POST parameters are used, do not use 'required=True' values in the protorpc Message definition
as this will fail on one of the sets of parms
"""
def jsonMethodHandler(handler):
def jsonMethodInner(self, **kwargs):
requestObject = getJsonRequestObject(self, requestType, http_method, kwargs)
logging.info(u'request={0}'.format(requestObject))
response = handler(self, requestObject) # Response object
if response:
# Convert response to Json
responseJson = protojson.encode_message(response)
else:
responseJson = '{}'
logging.info(u'response json={0}'.format(responseJson))
if self.response.headers:
self.response.headers['Content-Type'] = 'application/json'
if responseJson:
self.response.write(responseJson)
self.response.write('')
return jsonMethodInner
return jsonMethodHandler
The jsonMethod decorator uses a protorpc message for 'requestType' and 'responseType'.
I have constrained the http_method to be either GET, POST or DELETE for a method; you may wish to change this.
Note that this decorator must be applied to instance methods on a webapp2.RequestHandler class (see the example below) as it needs to access the webapp2 request and response objects.
The protorpc message is populated in getJsonRequestObject():
def getJsonRequestObject(self, requestType, http_method, kwargs):
"kwargs: URL keywords eg: /api/test/<key:\d+> => key"
"request.GET: used for 'GET' URL query string arguments"
"request.body: used for 'POST' or 'DELETE' form fields"
logging.info(u'URL parameters: {0}'.format(kwargs))
if http_method == 'POST' or http_method == 'DELETE':
requestJson = self.request.body
if requestJson == None:
requestJson = '' # Cater for no body (eg: IE10)
try:
logging.info("Content type = {}".format(self.request.content_type))
logRequest = requestJson if len(requestJson) < 1024 else requestJson[0:1024]
try:
logging.info(u'Request JSON: {0}'.format(logRequest))
except:
logging.info("Cannot log request JSON (invalid character?)")
postRequestObject = protojson.decode_message(requestType, requestJson)
except:
logError()
raise
if self.request.query_string:
# combine POST and GET parameters [GET query string overrides POST field]
getRequestObject = protourlencode.decode_message(requestType, self.request.query_string)
requestObject = combineRequestObjects(requestType, getRequestObject, postRequestObject)
else:
requestObject = postRequestObject
elif http_method == 'GET':
logging.info(u'Query strings: {0}'.format(self.request.query_string))
requestObject = protourlencode.decode_message(requestType, self.request.query_string)
logging.info(u'Request object: {0}'.format(requestObject))
else:
raise ValidationException(u'Invalid HTTP method: {0}'.format(http_method))
if len(kwargs) > 0:
#Combine URL keywords (kwargs) with requestObject
queryString = urllib.urlencode(kwargs)
keywordRequestObject = protourlencode.decode_message(requestType, queryString)
requestObject = combineRequestObjects(requestType, requestObject, keywordRequestObject)
return requestObject
getJsonRequestObject() handles GET, POST and webapp2 URL arguments (note: these are entered as kwargs).
combineRequestObjects() combines two objects of the requestType message:
def combineRequestObjects(requestType, request1, request2):
"""
Combines two objects of requestType; Note that request2 values will override request1 values if duplicated
"""
members = inspect.getmembers(requestType, lambda a:not(inspect.isroutine(a)))
members = [m for m in members if not m[0].startswith('_')]
for key, value in members:
val = getattr(request2, key)
if val:
setattr(request1, key, val)
return request1
Finally, a decorated webapp2 method example:
from protorpc import messages, message_types
class FileSearchRequest(messages.Message):
"""
A combination of file metadata and file information
"""
filename = messages.StringField(1)
startDateTime = message_types.DateTimeField(2)
endDateTime = message_types.DateTimeField(3)
class ListResponse(messages.Message):
"""
List of strings response
"""
items = messages.StringField(1, repeated=True)
...
class FileHandler(webapp2.RequestHandler):
#jsonMethod(FileSearchRequest, ListResponse, http_method='POST')
def searchFiles(self, request):
# Can now use request.filename etc
...
return ListResponse(items=items)
Hopefully this will give you some idea of how to go about implementing your own webapp2/protorpc framework.
You can also check and see how Cloud Endpoints is implementing their protorpc message handling. You may also need to dive into the protorpc code itself.
Please note that I have attempted to simplify my existing implementation, so you may come across various issues that you will need to address in your implementation.
In addition methods like 'logError()' and classes like 'ValidationException' are non-standard, so you will need to replace them as you see fit.
You may also wish to remove the logging at some point.
I have a problem making the simple (non-json) arguments work with POST. Just taking the simple example from their tutorials, I can't make a unit test where the task is passing as an argument. However task is never passed in. Its none.
class TodoList(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('task', type = str)
super(TodoList, self).__init__()
def post(self):
args = self.reqparse.parse_args()
#args['task'] is None, but why?
return TODOS[args['task']], 201
Unit test:
def test_task(self):
rv = self.app.post('todos', data='task=test')
self.check_content_type(rv.headers)
resp = json.loads(rv.data)
eq_(rv.status_code, 201)
What am I missing please?
When you use 'task=test' test_client do not set application/x-www-form-urlencoded content type, because you put string to input stream. So flask can't detect form and read data from form and reqparse will return None for any values for this case.
To fix it you must set up content type or use dict {'task': 'test'} or tuple.
Also for request testing better to use client = self.app.test_client() instead app = self.app.test_client(), if you use FlaskTesting.TestCase class, then just call self.client.post.
So, I've just started using mock with a Django project. I'm trying to mock out part of a view which makes a request to a remote API to confirm a subscription request was genuine (a form of verification as per the spec I'm working to).
What I have resembles:
class SubscriptionView(View):
def post(self, request, **kwargs):
remote_url = request.POST.get('remote_url')
if remote_url:
response = requests.get(remote_url, params={'verify': 'hello'})
if response.status_code != 200:
return HttpResponse('Verification of request failed')
What I now want to do is to use mock to mock out the requests.get call to change the response, but I can't work out how to do this for the patch decorator. I'd thought you do something like:
#patch(requests.get)
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object
How do I achieve this?
You're almost there. You're just calling it slightly incorrectly.
from mock import call, patch
#patch('my_app.views.requests')
def test_response_verify(self, mock_requests):
# We setup the mock, this may look like magic but it works, return_value is
# a special attribute on a mock, it is what is returned when it is called
# So this is saying we want the return value of requests.get to have an
# status code attribute of 200
mock_requests.get.return_value.status_code = 200
# Here we make the call to the view
response = SubscriptionView().post(request, {'remote_url': 'some_url'})
self.assertEqual(
mock_requests.get.call_args_list,
[call('some_url', params={'verify': 'hello'})]
)
You can also test that the response is the correct type and has the right content.
It's all in the documentation:
patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
target should be a string in the form ‘package.module.ClassName’.
from mock import patch
# or #patch('requests.get')
#patch.object(requests, 'get')
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object